English Русский 中文 Deutsch 日本語 Português
preview
DoEasy. Elementos de control (Parte 22): SplitContainer. Cambiando las propiedades del objeto creado

DoEasy. Elementos de control (Parte 22): SplitContainer. Cambiando las propiedades del objeto creado

MetaTrader 5Ejemplos | 27 enero 2023, 16:30
170 0
Artyom Trishkin
Artyom Trishkin

Contenido


Concepto

El control SplitContainer de la biblioteca se crea usando los valores predeterminados. Podemos cambiar las propiedades de un objeto tras crear este, pero su aspecto no cambiará. Para evitar que esto suceda, después de cambiar la propiedad de un objeto, lo redibujaremos con los nuevos valores de sus propiedades.

Hoy veremos un pequeño artículo en el que finalizaremos los métodos encargados de ajustar las propiedades de un control para que todos los cambios en dichas propiedades puedan modificar su aspecto de inmediato.


Mejorando las clases de la biblioteca

En el archivo \MQL5\Include\DoEasy\Defines.mqh, añadiremos a la lista de posibles eventos para los controles de WinForms dos nuevos eventos:

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

Algunos controles deberán reaccionar a su movimiento y enviar eventos al programa o al control padre en el que trabajan. Por ejemplo, un control deslizante en el área de desplazamiento, al ser capturado y desplazado con el ratón, deberá enviar un evento sobre su movimiento y los valores en los que se ha movido. De forma similar, el separador en el control SplitContainer deberá reaccionar a su movimiento.

En su implementación actual, cambiará su aspecto al pasar el cursor del ratón sobre él, y cuando se mueva, enviará un evento al elemento principal, que reaccionará a este evento modificando el tamaño de sus paneles. Posteriormente, mejoraremos la implementación de este comportamiento, y el objeto, al pasar el ratón sobre él, se trazará con un rectángulo de línea punteada, y cuando sea capturado por el ratón, se rellenará con un área sombreada. Tras soltar el ratón (completar el movimiento), el separador volverá a su aspecto original.

Para que podamos responder a tiempo y correctamente a dichos eventos (desplazamiento de un control y finalización de su movimiento), hemos añadido dos nuevos eventos. En consecuencia, hemos eliminado de esta lista el evento más reciente: WF_CONTROL_EVENT_SPLITTER_MOVE, ya que ahora podrá ser reemplazado por el evento WF_CONTROL_EVENT_MOVING, que es universal para todos los elementos. Y como el último evento de la lista ahora es el evento WF_CONTROL_EVENT_CLICK_SCROLL_DOWN, lo añadiremos al cálculo del valor del código del próximo evento.


Si ejecutamos el asesor del artículo anterior y comenzamos a mover el panel principal, su separador no será visible en el control SplitContainer (esto es correcto), deberá permanecer oculto hasta que el cursor del ratón se desplace sobre él. Pero si el objeto separador se desplaza al menos una vez, cualquier movimiento del panel principal hará que se muestre este control oculto.

Este es un error lógico que ha resultado difícil de encontrar. Sin embargo, ahora hemos encontrado la causa y también la solución: el método BringToTop() funciona de tal forma que primero oculta el objeto y luego lo muestra, llevándolo así al primer plano. Precisamente en este método el objeto separador oculto se hacía visible, ya que el método no tiene en cuenta el estado de la bandera que indica la necesidad de dibujar el elemento.

En el archivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh, en el método que coloca un objeto por encima de todos, añadiremos la comprobación de la bandera que indica la necesidad de dibujar un elemento y si el objeto no debe mostrarse, simplemente saldremos del 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();
     }
  }
//+------------------------------------------------------------------+

Y luego añadiremos la misma verificación para la lista con todos los objetos adjuntos; omitiremos aquellos objetos para los que se borra la bandera que indica la necesidad de dibujar, para no traer el objeto al primer plano.

En el controlador del último evento del ratón, añadiremos la comprobación de la bandera que indica la necesidad de dibujar el objeto:

//+------------------------------------------------------------------+
//| 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
      //---...
      //---...

Si anteriormente no se procesaba el último evento del ratón para los objetos ocultos e inactivos, ahora, además, aquellos objetos que no deban mostrarse no se procesarán.


En la clase de objeto de panel del control SplitContainer, en el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\SplitContainerPanel.mqh, añadiremos la comprobación de la bandera que indica la necesidad de dibujar el objeto en los métodos para limpiar un 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);
  }
//+------------------------------------------------------------------+

Estas comprobaciones también sirven para evitar que el panel se muestre involuntariamente si está oculto (contraído) en el control SplitContainer.

Si el separador del control SplitContainer tiene establecida la bandea de separador fijo, entonces no deberá interactuar con el ratón de ninguna manera. En consecuencia, si el cursor del ratón se coloca sobre el panel (el cursor ha dejado el separador y ha entrado en el panel), no será necesario procesar dicho evento para un separador fijo.

En el manejador del evento "El cursor está dentro del área activa, los botones del ratón no están presionados", añadiremos la comprobación de la bandera que indica la separación fija:

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

Esta comprobación, de hecho, simplemente nos evitará acciones innecesarias, pues no deberemos obtener un objeto separador y ocultarlo si ya se encuentra oculto desde el principio.

Todos los cambios y mejoras importantes se realizarán en el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\SplitContainer.mqh del objeto WinForms SplitContainer.

En la sección privada de la clase, declararemos los nuevos métodos para contraer/expandir los paneles de los elementos:

//--- 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:


Fuera del cuerpo de la clase, escribiremos la implementación de los métodos declarados.

El panel contraído se ocultará, mientras que para su propiedad encargada de indicar la necesidad de representación se establecerá el valor 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 el panel expandido, estableceremos la necesidad de mostrarlo, mostrar el panel y traerlo al primer 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();
  }
//+------------------------------------------------------------------+


Algunos de los métodos encargados de establecer propiedades en los objetos de la biblioteca pueden simplemente establecer un valor en una propiedad o establecer un valor en una propiedad y establecerlo en un objeto gráfico. Para los métodos de esta clase, también lo haremos posible: simplemente escribiremos el valor en la propiedad del objeto o, después de escribir el valor en la propiedad, redibujaremos todo el objeto, ya que cambiar la propiedad debería provocar un cambio en el aspecto del control SplitContainer.

Después añadiremos a los parámetros formales de los métodos una bandera que indicará que solo se necesita establecer el valor en la propiedad del objeto y trasladaremos la implementación del método fuera del cuerpo de la clase, eliminándolo aquí:

//--- (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 el objeto de clase maneje de forma independiente la eliminación del cursor del ratón del área del separador, deberemos añadir un manejador virtual para el último evento del ratón. Después lo declararemos en la sección pública de la clase:

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


En el constructor de la clase, estableceremos el valor predeterminado de la propiedad del separador fijo en "móvil":

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

Después de crear el objeto, el separador se podrá desplazar de forma predeterminada. Si luego deseamos que el separador no se pueda desplazar, solo deberemos establecer esta propiedad en true usando el mismo método.


El método que establece los parámetros para los paneles ha cambiado.
Ahora, para todos los paneles ocultos se establecerán unas dimensiones iguales a las del panel no oculto, mientras que el panel, que al mismo tiempo permanecerá visible, las dimensiones se volverán iguales al tamaño de su contenedor, es decir, al tamaño completo del 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 establece la bandera de contracción para el panel 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();
        }
     }
  }
//+------------------------------------------------------------------+

La lógica del método se comenta con todo detalle en el código. A continuación, o bien se transmitirá al método la bandera con el valor true, ocultándose el panel1, mostrándose el panel2 y expandiendo este hasta el tamaño completo del contenedor, o bien se transmitirá al método la bandera con el valor false, ocultándose el panel2 y expandiendo el panel 1 a toda la magnitud del contenedor.


Método que establece la bandera de contracción para el panel 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();
        }
     }
  }
//+------------------------------------------------------------------+

La lógica del método es similar a la lógica del método anterior, pero las banderas se configurarán para el panel2, mientras que el panel1 se ocultará/mostrará dependiendo de si el panel2 está contraído o expandido.


Método que establece la distancia del separador respecto al borde:

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

La mejora del método consistirá en que si se ha transmitido la bandera only_prop (establecer solo la propiedad) con un valor false, además de establecerse un nuevo valor en la propiedad "distancia del separador", necesitaremos establecer las nuevas coordenadas y tamaños para los paneles y el separador, así como reconstruir los paneles según el nuevo valor de distancia del separador.


Método que establece el grosor del 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());
                 }
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

La lógica de la mejora del método es similar a la lógica de la mejora del método anterior.


Método que establece la ubicación del separador:

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

La lógica del método resulta idéntica a la lógica de los métodos anteriores. Primero, estableceremos la propiedad del objeto en el valor transmitido al método, y luego, si la bandera only_prop se ha establecido en false, ajustaremos los parámetros de los paneles y el separador y reorganizaremos la ubicación de los paneles y el separador según se hayan ajustado las propiedades de sus tamaños y coordenadas.


Como ahora los métodos que cambian las propiedades del objeto pueden reconstruir inmediatamente la ubicación de los paneles y el separador, el manejador de eventos se ha acortado un poco:

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

Si el separador es fijo, entonces el evento de desplazamiento del elemento no deberá procesarse, por lo que este valor se verificará y el manejador saldrá si el separador es fijo. El método SetSplitterDistance() realizará aquí una doble tarea: establecerá las nuevas coordenadas del separador y reconstruirá los paneles del objeto, ya que el indicador only_prop se ha establecido en false al llamar al método.


El manejador de eventos para el "Cursor se encuentra dentro del área activa, los botones del ratón no están presionados" recibirá el puntero al objeto separador y lo mostrará de forma sombreada. Si el separador del control SplitContainer es fijo, el objeto separador no debería aparecer.
Por consiguiente, al inicio del controlador, añadiremos la siguiente verificación: si el separador es fijo, saldremos del 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);
     }
  }
//+------------------------------------------------------------------+


En todos los elementos gráficos, siempre podremos ver cuál ha sido el último evento del ratón para el objeto. Para hacer esto, la clase de objeto de formulario tendrá el método virtual OnMouseEventPostProcessing(), que podremos redefinir en las clases derivadas si la lógica del método de la clase principal no resulta adecuada para procesar el último evento del ratón, lo cual hemos hecho en esta clase.

Manejador del último evento del ratón:

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

Si el último evento es la eliminación del cursor del ratón del área del objeto, entonces obtendremos el puntero al objeto separador, estableceremos la bandera de no representación, ocultaremos el separador y estableceremos el estado actual del ratón como el estado pasado.
Ahora, al alejar el ratón del control SplitContainer, su objeto separador se ocultará. Más tarde, añadiremos aquí el procesamiento del desplazamiento del cursor del ratón desde el área del separador, para no hacerlo en los objetos de panel del control SplitContainer.


En el archivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh de la clase de colección de elementos gráficos, añadiremos al método FormPostProcessing() la verificación de la bandera de no representación del objeto, ya que los objetos que no deben mostrarse tampoco deberán ser procesados por este manejador para evitar su representación 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());
  }
//+------------------------------------------------------------------+

Esas son todas las mejoras realizadas por hoy. Veamos lo que tenemos ahora.


Simulación

Para la prueba, tomaremos el asesor del artículo anterior y lo guardaremos en la nueva carpeta \MQL5\Experts\TestDoEasy\Part122\ con el nuevo nombre TestDoEasy122.mq5.

¿Cómo realizaremos la prueba? Empezaremos creando en nuestro asesor un panel en el que se construirá el control TabControl, creando además en cada una de sus primeras cinco pestañas el control SplitContainer. En los índices pares de pestañas, el separador será vertical, mientras que en los índices impares, será horizontal. En la tercera pestaña, haremos que el separador sea fijo, y en la cuarta y quinta pestañas, contraeremos el panel2 y el panel1, respectivamente.

El manejador OnInit() del asesor ahora se verá así:

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

La lógica del controlador se comenta con todo detalle en el código.

En un ciclo por el número de pestañas en las que necesitaremos construir los controles SplitContainer, crearemos un nuevo objeto en cada pestañay, después de crearlo, obtendremos un puntero y estableceremos los nuevos parámetros para él.

Vamos a compilar el asesor y a ejecutarlo en el gráfico:



Como podemos ver, todas las nuevas propiedades establecidas en el objeto después de su creación realizan correctamente cambios en su aspecto.

Entre los defectos, en primer lugar, destaca la operación difusa de ocultación del objeto separador después de alejar el cursor del ratón, pero cambiaremos su lógica de comportamiento y representación para que coincida más con su lógica de comportamiento en MS Visual Studio, y luego entenderemos el problema.


¿Qué es lo próximo?

En el próximo artículo continuaremos trabajando con el control SplitContainer.

Más abajo, adjuntamos todos los archivos de la versión actual de la biblioteca, así como los archivos del asesor de prueba y el indicador de control de eventos de los gráficos para MQL5. Podrá descargarlo todo y ponerlo a prueba por sí mismo. Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.

Volver al contenido

*Artículos de esta serie:

 
DoEasy. Elementos de control (Parte 13): Optimizando la interacción de los objetos WinForms con el ratón. Comenzamos el desarrollo del objeto WinForms TabControl
DoEasy. Elementos de control (Parte 14): Nuevo algoritmo de denominación de los elementos gráficos. Continuamos trabajando con el objeto WinForms TabControl
DoEasy. Elementos de control (Parte 15): Objeto WinForms TabControl - múltiples filas de encabezados de pestañas, métodos de trabajo con pestañas 
DoEasy. Elementos de control (Parte 16): Objeto WinForms TabControl - múltiples filas de encabezados de pestañas, modo de expansión de encabezados para ajustarse al tamaño del contenedor
DoEasy. Elementos de control (Parte 17): Recortando partes invisibles de objetos, objetos WinForms auxiliares de botón con flechas
DoEasy. Elementos de control (Parte 18): Preparamos la funcionalidad para el scrolling de las pestañas en TabControl
DoEasy. Elementos de control (Parte 19): Scrolling de pestañas en el elemento TabControl, eventos de objetos WinForms
DoEasy. Elementos de control (Parte 20): El objeto WinForms SplitContainer
DoEasy. Elementos de control (Parte 21): Elemento de control SplitContainer. Separador de paneles



Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/11601

Archivos adjuntos |
MQL5.zip (4480.1 KB)
Cómo construir un EA que opere automáticamente (Parte 07): Tipos de cuentas (II) Cómo construir un EA que opere automáticamente (Parte 07): Tipos de cuentas (II)
Aprenda a crear un EA que opere automáticamente de forma sencilla y segura. Uno siempre debe estar al tanto de lo que está haciendo un EA automatizado, y si se descarrila, eliminarlo lo más rápido posible del gráfico, para poner fin a lo que él estaba haciendo y evitar que las cosas se salgan de control.
Algoritmos de optimización de la población: Enjambre de partículas (PSO) Algoritmos de optimización de la población: Enjambre de partículas (PSO)
En este artículo, analizaremos el popular algoritmo de optimización de la población «Enjambre de partículas» (PSO — particle swarm optimisation). Con anterioridad, ya discutimos características tan importantes de los algoritmos de optimización como la convergencia, la tasa de convergencia, la estabilidad, la escalabilidad, y también desarrollamos un banco de pruebas y analizamos el algoritmo RNG más simple.
DoEasy. Controles (Parte 23): mejorando los objetos WinForms TabControl y SplitContainer DoEasy. Controles (Parte 23): mejorando los objetos WinForms TabControl y SplitContainer
En este artículo, añadiremos los nuevos eventos de ratón respecto a los límites de los espacios de trabajo WinForms, y también corregiremos algunos errores en los controles TabControl y SplitContainer.
Cómo construir un EA que opere automáticamente (Parte 06): Tipos de cuentas (I) Cómo construir un EA que opere automáticamente (Parte 06): Tipos de cuentas (I)
Aprenda a crear un EA que opere automáticamente de forma sencilla y segura. Hasta ahora nuestro EA puede funcionar en cualquier tipo de situación, pero aún no está listo para ser automatizado, por lo que tenemos que hacer algunas cosas.