English Русский 中文 Español Deutsch 日本語
preview
DoEasy. Controles (Parte 22): SplitContainer. Alterando as propriedades do objeto criado

DoEasy. Controles (Parte 22): SplitContainer. Alterando as propriedades do objeto criado

MetaTrader 5Exemplos | 13 janeiro 2023, 09:37
252 0
Artyom Trishkin
Artyom Trishkin

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.

Todos os arquivos da versão atual da biblioteca, os arquivos do EA de teste e o indicador do controle de eventos dos gráficos para MQL5 estão anexados abaixo. Você pode baixá-los e testar tudo sozinho. Se você tiver dúvidas, comentários e sugestões, pode colocá-los nos comentários do artigo.

Voltar ao conteúdo

*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

Arquivos anexados |
MQL5.zip (4480.1 KB)
Como desenvolver um sistema de negociação baseado no indicador Oscilador Acelerador Como desenvolver um sistema de negociação baseado no indicador Oscilador Acelerador
Um novo artigo da nossa série sobre como criar sistemas de negociação simples pelos indicadores técnicos mais populares. Nós aprenderemos sobre o indicador Oscilador Acelerador e aprenderemos como desenvolver um sistema de negociação usando-o.
DoEasy. Controles (Parte 21): O controle SplitContainer. Separador de painéis DoEasy. Controles (Parte 21): O controle SplitContainer. Separador de painéis
Neste artigo, criaremos uma classe do objeto separador de painéis auxiliar para o controle SplitContainer.
Algoritmos de otimização populacionais: Enxame de partículas (PSO) Algoritmos de otimização populacionais: Enxame de partículas (PSO)
Neste artigo vamos analisar o popular algoritmo de otimização por enxame de partículas (PSO). Anteriormente, discutimos características importantes de algoritmos de otimização, como convergência, velocidade de convergência, estabilidade, escalabilidade e desenvolvemos uma bancada de testes. Também analisamos um algoritmo simples baseado em geradores de números aleatórios (GNA).
DoEasy. Controles (Parte 20): Objeto WinForms SplitContainer DoEasy. Controles (Parte 20): Objeto WinForms SplitContainer
Hoje começaremos a desenvolver o controle SplitContainer a partir da caixa de ferramentas do MS Visual Studio. Este elemento consiste em dois painéis separados por um separador móvel vertical ou horizontal.