English Русский 中文 Español Deutsch 日本語
preview
DoEasy. Controles (Parte 23): apurando os objetos WinForms TabControl e SplitContainer

DoEasy. Controles (Parte 23): apurando os objetos WinForms TabControl e SplitContainer

MetaTrader 5Exemplos | 20 janeiro 2023, 10:31
211 0
Artyom Trishkin
Artyom Trishkin

Conteúdo


Ideia

A biblioteca possui um modelo de eventos para "comunicação" entre elementos gráficos e o cursor do mouse. Cada elemento gráfico possui áreas de "trabalho", responsáveis por determinado comportamento ao interagir com o mouse. Por exemplo, cada objeto tem uma zona ativa, onde a interação com ele é possível. Também possui uma área de controle, com botões para interações como minimizar, expandir e fechar. Com base na presença dessa área, são acrescentadas funcionalidades adicionais ao interagir com ela. Por exemplo, quanto ao separador do controle SplitContainer, é possível operá-lo manipulando um evento dentro da área de controle correspondente à localização do separador.

Para implementar essa funcionalidade, incluiremos novos manipuladores de eventos de mouse para processar a interação do cursor dentro da área de controle. Isso permitirá que o separador do controle SplitContainer processe esses eventos. Além disso, corrigiremos as deficiências nos controles TabControl e SplitContainer.

Em geral, o processo de aperfeiçoamento contínuo e correção de erros nos controles nos leva a reformulações na lógica de criação de elementos gráficos. Por exemplo, a especificação dos objetos base e principal para um objeto recém-criado pode não funcionar corretamente na implementação atual da lógica para criar objetos anexados a um elemento gráfico. A transferência incorreta desses valores pode causar que o objeto "não reconheça" seu pai principal, o que leva a exibições incorretas de elementos gráficos quando alternados, se houver vários objetos painéis independentes no gráfico.

Ao encontrar e corrigir a indicação do elemento principal para um objeto gráfico, sugere-se cada vez mais refletir sobre a forma como esses dados são passados para o objeto, não somente depois de sua criação, mas diretamente no momento da criação do objeto gráfico, através de seu construtor. Esse conceito será testado e, provavelmente, implementado em futuros artigos.


Modificando as classes da biblioteca

SplitContainer agora é uma área de controle que pode ser movida e redimensionada. Por essa razão, iremos renomear as constantes na enumeração que descrevem os estados do mouse em relação à forma e ao evento do mouse.

No arquivo \MQL5\Include\DoEasy\Defines.mqh, vamos alterar os nomes das constantes das enumerações acima:

//--- Within the window separator area
   MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_NOT_PRESSED, // The cursor is within the window resizing area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_PRESSED,     // The cursor is within the window resizing area, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_WHEEL,       // The cursor is within the window separator area, the mouse wheel is being scrolled
  };
//+------------------------------------------------------------------+

...

//--- Within the window separator area
   MOUSE_EVENT_INSIDE_SPLITTER_AREA_NOT_PRESSED,      // The cursor is within the window resizing area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_SPLITTER_AREA_PRESSED,          // The cursor is within the window resizing area, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_SPLITTER_AREA_WHEEL,            // The cursor is within the window separator area, the mouse wheel is being scrolled
  };
#define MOUSE_EVENT_NEXT_CODE  (MOUSE_EVENT_INSIDE_SPLITTER_AREA_WHEEL+1)  // The code of the next event after the last mouse event code
//+------------------------------------------------------------------+

Os nomes das constantes de enumeração agora se referem à área de controle, o que faz sentido e engloba todos os objetos para os quais essas áreas serão definidas, independentemente do objetivo ou funcionalidade dessas mesmas:

//+------------------------------------------------------------------+
//| The list of possible mouse states relative to the form           |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_FORM_STATE
  {
   MOUSE_FORM_STATE_NONE = 0,                         // Undefined state
//--- Outside the form
   MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED,         // The cursor is outside the form, the mouse buttons are not clicked
   MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED,             // The cursor is outside the form, the mouse button (any) is clicked
   MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL,               // The cursor is outside the form, the mouse wheel is being scrolled
//--- Within the form
   MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED,          // The cursor is inside the form, no mouse buttons are clicked
   MOUSE_FORM_STATE_INSIDE_FORM_PRESSED,              // The cursor is inside the form, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_FORM_WHEEL,                // The cursor is inside the form, the mouse wheel is being scrolled
//--- Within the window header area
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED,   // The cursor is inside the active area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED,       // The cursor is inside the active area,  any mouse button is clicked
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL,         // The cursor is inside the active area, the mouse wheel is being scrolled
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED,      // The cursor is inside the active area, left mouse button is released
//--- Within the window scrolling area
   MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED,   // The cursor is within the window scrolling area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED,       // The cursor is within the window scrolling area, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL,         // The cursor is within the window scrolling area, the mouse wheel is being scrolled
//--- Within the window resizing area
   MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_NOT_PRESSED,   // The cursor is within the window resizing area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_PRESSED,       // The cursor is within the window resizing area, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_WHEEL,         // The cursor is within the window resizing area, the mouse wheel is being scrolled
//--- Within the control area
   MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_NOT_PRESSED,  // The cursor is within the control area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED,      // The cursor is within the control area, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL,        // The cursor is within the control area, the mouse wheel is being scrolled
  };
//+------------------------------------------------------------------+
//| List of possible mouse events                                    |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_EVENT
  {
   MOUSE_EVENT_NO_EVENT = CHART_OBJ_EVENTS_NEXT_CODE, // No event
//---
   MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED,              // The cursor is outside the form, the mouse buttons are not clicked
   MOUSE_EVENT_OUTSIDE_FORM_PRESSED,                  // The cursor is outside the form, the mouse button (any) is clicked
   MOUSE_EVENT_OUTSIDE_FORM_WHEEL,                    // The cursor is outside the form, the mouse wheel is being scrolled
//--- Within the form
   MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED,               // The cursor is inside the form, no mouse buttons are clicked
   MOUSE_EVENT_INSIDE_FORM_PRESSED,                   // The cursor is inside the form, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_FORM_WHEEL,                     // The cursor is inside the form, the mouse wheel is being scrolled
//--- Within the window active area
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED,        // The cursor is inside the active area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_PRESSED,            // The cursor is inside the active area, any mouse button is clicked
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_WHEEL,              // The cursor is inside the active area, the mouse wheel is being scrolled
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_RELEASED,           // The cursor is inside the active area, left mouse button is released
//--- Within the window scrolling area
   MOUSE_EVENT_INSIDE_SCROLL_AREA_NOT_PRESSED,        // The cursor is within the window scrolling area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_SCROLL_AREA_PRESSED,            // The cursor is within the window scrolling area, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL,              // The cursor is within the window scrolling area, the mouse wheel is being scrolled
//--- Within the window resizing area
   MOUSE_EVENT_INSIDE_RESIZE_AREA_NOT_PRESSED,        // The cursor is within the window resizing area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_RESIZE_AREA_PRESSED,            // The cursor is within the window resizing area, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_RESIZE_AREA_WHEEL,              // The cursor is within the window resizing area, the mouse wheel is being scrolled
//--- Within the control area
   MOUSE_EVENT_INSIDE_CONTROL_AREA_NOT_PRESSED,       // The cursor is within the control area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_CONTROL_AREA_PRESSED,           // The cursor is within the control area, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL,             // The cursor is within the control area, the mouse wheel is being scrolled
  };
#define MOUSE_EVENT_NEXT_CODE  (MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL+1)   // The code of the next event after the last mouse event code
//+------------------------------------------------------------------+

O valor da macro substituição MOUSE_EVENT_NEXT_CODE agora é calculado a partir do valor da última constante na enumeração de eventos de mouse possíveis.


Os elementos gráficos na biblioteca possuem um escopo. Se um objeto gráfico estiver anexado a outro e alguma parte dele ultrapassar o objeto pai, essa parte será cortada.  Para controlar essas situações, precisamos criar um método que verifique se o cursor está dentro da parte visível do objeto gráfico e retorne o resultado como um sinalizador,  evitando o envio de eventos de interação para partes recortadas (invisíveis) do objeto.

Para definir as coordenadas do início da área de controle e suas dimensões (largura e altura), precisamos adicionar tais métodos.

No arquivo da classe do elemento gráfico \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, declaramos/adicionamos os seguintes métodos na seção pública da classe:

//--- (1) Save the graphical resource to the array and (2) restore the resource from the array
   bool              ResourceStamp(const string source);
   virtual bool      Reset(void);
   
//--- Return the cursor position relative to the (1) entire element, (2) visible part, (3) active area and (4) element control area
   bool              CursorInsideElement(const int x,const int y);
   bool              CursorInsideVisibleArea(const int x,const int y);
   bool              CursorInsideActiveArea(const int x,const int y);
   bool              CursorInsideControlArea(const int x,const int y);

//--- Create the element

...

//--- Set (1) object movability, (2) activity, (3) interaction,
//--- (4) element ID, (5) element index in the list, (6) availability and (7) shadow flag
   void              SetMovable(const bool flag)               { this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,flag);                     }
   void              SetActive(const bool flag)                { this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,flag);                      }
   void              SetInteraction(const bool flag)           { this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,flag);                 }
   void              SetID(const int id)                       { this.SetProperty(CANV_ELEMENT_PROP_ID,id);                            }
   void              SetNumber(const int number)               { this.SetProperty(CANV_ELEMENT_PROP_NUM,number);                       }
   void              SetEnabled(const bool flag)               { this.SetProperty(CANV_ELEMENT_PROP_ENABLED,flag);                     }
   void              SetShadow(const bool flag)                { this.m_shadow=flag;                                                   }
   
//--- Set the (1) X, (2) Y coordinates, (3) width and (4) height of the element control area
   void              SetControlAreaX(const int value)          { this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,value);             }
   void              SetControlAreaY(const int value)          { this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,value);             }
   void              SetControlAreaWidth(const int value)      { this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,value);         }
   void              SetControlAreaHeight(const int value)     { this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,value);        }
   
//--- Return the shift (1) of the left, (2) right, (3) top and (4) bottom edge of the element active area
   int               ActiveAreaLeftShift(void)           const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT);       }
   int               ActiveAreaRightShift(void)          const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT);      }
   int               ActiveAreaTopShift(void)            const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP);        }
   int               ActiveAreaBottomShift(void)         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM);     }
//--- Return the coordinate (1) of the left, (2) right, (3) top and (4) bottom edge of the element active area
   int               ActiveAreaLeft(void)                const { return int(this.CoordX()+this.ActiveAreaLeftShift());                 }
   int               ActiveAreaRight(void)               const { return int(this.RightEdge()-this.ActiveAreaRightShift());             }
   int               ActiveAreaTop(void)                 const { return int(this.CoordY()+this.ActiveAreaTopShift());                  }
   int               ActiveAreaBottom(void)              const { return int(this.BottomEdge()-this.ActiveAreaBottomShift());           }

//--- Return (1) X, (2) Y coordinate shift, (3) width, (4) height, (5) right and (6) lower edge of the control management area
   int               ControlAreaXShift(void)             const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X);       }
   int               ControlAreaYShift(void)             const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y);       }
   int               ControlAreaWidth(void)              const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH);   }
   int               ControlAreaHeight(void)             const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT);  }
//--- Return the coordinate (1) of the left, (2) right, (3) top and (4) bottom edge of the element control area
   int               ControlAreaLeft(void)               const { return this.CoordX()+this.ControlAreaXShift();                        }
   int               ControlAreaRight(void)              const { return this.ControlAreaLeft()+this.ControlAreaWidth();                }
   int               ControlAreaTop(void)                const { return this.CoordY()+this.ControlAreaYShift();                        }
   int               ControlAreaBottom(void)             const { return this.ControlAreaTop()+this.ControlAreaHeight();                }
//--- Return the relative coordinate (1) of the left, (2) right, (3) top and (4) bottom edge of the element control area
   int               ControlAreaLeftRelative(void)       const { return this.ControlAreaLeft()-this.CoordX();                          }
   int               ControlAreaRightRelative(void)      const { return this.ControlAreaRight()-this.CoordX();                         }
   int               ControlAreaTopRelative(void)        const { return this.ControlAreaTop()-this.CoordY();                           }
   int               ControlAreaBottomRelative(void)     const { return this.ControlAreaBottom()-this.CoordY();                        }
   
//--- Return the (1) X, (2) Y coordinates, (3) width and (4) height of the element right scroll area height

...

//--- Visibility scope height
   virtual int       VisibleAreaHeight(void)             const { return this.YSize();                                                  }
   virtual bool      SetVisibleAreaHeight(const int value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && CGBaseObj::SetYSize(value)) || only_prop)
                          {
                           this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,value);
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Set relative coordinates and size of the visible area
   void              SetVisibleArea(const int x,const int y,const int w,const int h)
                       {
                        this.SetVisibleAreaX(x,false);
                        this.SetVisibleAreaY(y,false);
                        this.SetVisibleAreaWidth(w,false);
                        this.SetVisibleAreaHeight(h,false);
                       }
//--- Sets the size of the visible area equal to the entire object
   void              ResetVisibleArea(void)                    { this.SetVisibleArea(0,0,this.Width(),this.Height());                  }
                       
//--- Return the (1) X coordinate, (2) right border, (3) Y coordinate, (4) bottom border of the visible area 

Esses métodos são usados para simplificar a configuração e obtenção de propriedades do escopo do objeto gráfico.


Implementação do método que retorna a posição do cursor em relação à área visível do elemento:

//+-----------------------------------------------------------------------------+
//|Return the position of the cursor relative to the visible area of the element|
//+-----------------------------------------------------------------------------+
bool CGCnvElement::CursorInsideVisibleArea(const int x,const int y)
  {
   return(x>=this.CoordXVisibleArea() && x<=this.RightEdgeVisibleArea() && y>=this.CoordYVisibleArea() && y<=this.BottomEdgeVisibleArea());
  }
//+------------------------------------------------------------------+

O método aceita as coordenadas atuais do cursor do mouse e retorna um sinalizador indicando as coordenadas especificadas dentro do escopo delimitado pelas coordenadas obtidas usando os métodos acima.


Método que retorna a posição do cursor em relação à área de controle do elemento:

//+------------------------------------------------------------------+
//|Return the cursor position relative to the element control area   |
//+------------------------------------------------------------------+
bool CGCnvElement::CursorInsideControlArea(const int x,const int y)
  {
   return(x>=this.ControlAreaLeft() && x<=this.ControlAreaRight() && y>=this.ControlAreaTop() && y<=this.ControlAreaBottom());
  }
//+------------------------------------------------------------------+

Este é um método adicionado anteriormente modificado. Agora ele usa os valores das coordenadas da área de controle obtidas pelos métodos escritos acima.

A declaração dos manipuladores de eventos de mouse virtual está localizada na seção protegida da classe do objeto forma, no arquivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh.

Todos esses manipuladores não fazem nada aqui e devem ser redefinidos nas classes herdadas. Vamos adicionar à sua lista a declaração de novos manipuladores para o cursor localizado na área de controle do elemento gráfico:

//--- 'The cursor is inside the window scrolling area, a mouse button is clicked (any)' event handler
   virtual void      MouseScrollAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the window scrolling area, the mouse wheel is being scrolled' event handler
   virtual void      MouseScrollAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the control area, no mouse buttons are clicked' event handler
   virtual void      MouseControlAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the control area, a mouse button is clicked (any)' event handler
   virtual void      MouseControlAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the control area, the mouse wheel is being scrolled' event handler
   virtual void      MouseControlAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- Send a message about the event
   virtual bool      SendEvent(const long chart_id,const ushort event_id);

public:


No método que cria um novo elemento anexado, adicionamos uma indicação do objeto base e principal:

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CForm::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                             const int x,
                             const int y,
                             const int w,
                             const int h,
                             const color colour,
                             const uchar opacity,
                             const bool activity,
                             const bool redraw)
  {
//--- Create a new graphical element
   CGCnvElement *obj=this.CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity);
//--- If the object has been created, draw the added object and return 'true'
   if(obj==NULL)
      return false;
   obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain());
   obj.SetBase(this.GetBase());
   obj.Erase(colour,opacity,redraw);
   return true;
  }
//+------------------------------------------------------------------+

Esse objeto contém ponteiros para os objetos principal e base. Quando um novo objeto gráfico é anexado a ele, os objetos principal e base são indicados. Infelizmente, esse método não funciona para todos os elementos gráficos e a busca por erros ainda não deu resultados. É necessário alterar essa abordagem de escrita dos objetos principal e base. Além disso, à medida que o número de elementos gráficos aumenta, é importante garantir a precisão na entrada dos objetos principal e base em todos os novos objetos da biblioteca, o que não é atualmente possível. A abordagem deve ser criar uma solução funcional única que funcione para todos os novos objetos da biblioteca, evitando a necessidade constante de encontrar e corrigir erros ao adicionar ponteiros para os objetos principal e base de maneira diferente para cada novo objeto.

Apuramos o método que define e retorna o estado do mouse em relação à forma. Ele deve incluir a verificação do cursor estando dentro da área de controle, bem como o gerenciamento de pressionamentos de botões e rolagem da roda do mouse. Para melhorar a usabilidade, também adicionaremos uma tabela que descreve os sinalizadores de estado do mouse:

//+------------------------------------------------------------------+
//| Set and get the mouse status relative to the form                |
//+------------------------------------------------------------------+
ENUM_MOUSE_FORM_STATE CForm::MouseFormState(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Data location in the ushort value of the button status
   //---------------------------------------------------------------------------
   //   bit    |    byte   |            state            |    dec    |   hex   |
   //---------------------------------------------------------------------------
   //    0     |     0     | left mouse button           |     1     |    1    |
   //---------------------------------------------------------------------------
   //    1     |     0     | right mouse button          |     2     |    2    |
   //---------------------------------------------------------------------------
   //    2     |     0     | SHIFT key                   |     4     |    4    |
   //---------------------------------------------------------------------------
   //    3     |     0     | CTRL key                    |     8     |    8    |
   //---------------------------------------------------------------------------
   //    4     |     0     | middle mouse button         |    16     |   10    |
   //---------------------------------------------------------------------------
   //    5     |     0     | 1 add. mouse button         |    32     |   20    |
   //---------------------------------------------------------------------------
   //    6     |     0     | 2 add. mouse button         |    64     |   40    |
   //---------------------------------------------------------------------------
   //    7     |     0     | scrolling the wheel         |    128    |   80    |
   //---------------------------------------------------------------------------
   //---------------------------------------------------------------------------
   //    0     |     1     | cursor inside the form      |    256    |   100   |
   //---------------------------------------------------------------------------
   //    1     |     1     | cursor inside active area   |    512    |   200   |
   //---------------------------------------------------------------------------
   //    2     |     1     | cursor in the control area  |   1024    |   400   |
   //---------------------------------------------------------------------------
   //    3     |     1     | cursor in the scrolling area|   2048    |   800   |
   //---------------------------------------------------------------------------
   //    4     |     1     | cursor at the left edge     |   4096    |  1000   |
   //---------------------------------------------------------------------------
   //    5     |     1     | cursor at the bottom edge   |   8192    |  2000   |
   //---------------------------------------------------------------------------
   //    6     |     1     | cursor at the right edge    |   16384   |  4000   |
   //---------------------------------------------------------------------------
   //    7     |     1     | cursor at the top edge      |   32768   |  8000   |
   //---------------------------------------------------------------------------
//--- Get the mouse status relative to the form, as well as the states of mouse buttons and Shift/Ctrl keys
   this.m_mouse_form_state=MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED;
   ENUM_MOUSE_BUTT_KEY_STATE state=this.m_mouse.ButtonKeyState(id,lparam,dparam,sparam);
//--- Get the mouse status flags from the CMouseState class object and save them in the variable
   this.m_mouse_state_flags=this.m_mouse.GetMouseFlags();
//--- If the cursor is inside the form
   if(CGCnvElement::CursorInsideElement(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
     {
      //--- Set bit 8 responsible for the "cursor inside the form" flag
      this.m_mouse_state_flags |= (0x0001<<8);
      
      //--- If the cursor is inside the active area, set bit 9 "cursor inside the active area"
      if(CGCnvElement::CursorInsideActiveArea(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
         this.m_mouse_state_flags |= (0x0001<<9);
      //--- otherwise, release the bit "cursor inside the active area"
      else this.m_mouse_state_flags &=0xFDFF;
      
      //--- If the cursor is inside the control area, set bit 10 "cursor inside the control area",
      if(CGCnvElement::CursorInsideControlArea(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
         this.m_mouse_state_flags |= (0x0001<<10);
      //--- otherwise, remove the "cursor inside the control area" bit
      else this.m_mouse_state_flags &=0xFBFF;
      
      //--- If one of the three mouse buttons is pressed, check the location of the cursor in the form areas and
      //--- return the appropriate value of the pressed key (in the active, control or form area)
      if((this.m_mouse_state_flags & 0x0001)!=0 || (this.m_mouse_state_flags & 0x0002)!=0 || (this.m_mouse_state_flags & 0x0010)!=0)
        {
         //--- If the cursor is inside the form
         if((this.m_mouse_state_flags & 0x0100)!=0)
            this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_FORM_PRESSED;
         //--- If the cursor is inside the active area of the form
         if((this.m_mouse_state_flags & 0x0200)!=0)
            this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED;
         //--- If the cursor is inside the form control area
         if((this.m_mouse_state_flags & 0x0400)!=0)
            this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED;
        }
      
      //--- otherwise, if not a single mouse button is pressed
      else
        {
         //--- if the mouse wheel is scrolled, return the appropriate wheel scrolling value (in the active, control or form area)
         //--- If the cursor is inside the form
         if((this.m_mouse_state_flags & 0x0100)!=0)
           {
            //--- If the mouse wheel is being scrolled
            if((this.m_mouse_state_flags & 0x0080)!=0)
               this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_FORM_WHEEL;
            else
               this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED;
           }
         //--- If the cursor is inside the active area of the form
         if((this.m_mouse_state_flags & 0x0200)!=0)
           {
            //--- If the mouse wheel is being scrolled
            if((this.m_mouse_state_flags & 0x0080)!=0)
               this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL;
            else
               this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED;
           }
         //--- If the cursor is inside the form control area
         if((this.m_mouse_state_flags & 0x0400)!=0)
           {
            //--- If the mouse wheel is being scrolled
            if((this.m_mouse_state_flags & 0x0080)!=0)
               this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL;
            else
               this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_NOT_PRESSED;
           }
        } 
     }
//--- If the cursor is outside the form
   else
     {
      //--- return the appropriate button value in an inactive area
      this.m_mouse_form_state=
        (
         ((this.m_mouse_state_flags & 0x0001)!=0 || (this.m_mouse_state_flags & 0x0002)!=0 || (this.m_mouse_state_flags & 0x0010)!=0) ? 
          MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED
        );
     }
   return this.m_mouse_form_state;
  }
//+------------------------------------------------------------------+

A lógica do método está descrita nos comentários. Usando o estado dos sinalizadores de bit na variável m_mouse_state_flags, determinamos se o botão do mouse está pressionado ou não. Também usamos esses sinalizadores para determinar a posição do cursor em uma determinada área do objeto gráfico e retornar o estado final do cursor, botões e roda do mouse em relação à forma.


Adicionamos um tratamento de novo eventos ao manipulador de eventos do mouse:

//+------------------------------------------------------------------+
//| Mouse event handler                                              |
//+------------------------------------------------------------------+
void CForm::OnMouseEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   switch(id)
     {
      //--- 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_EVENT_OUTSIDE_FORM_NOT_PRESSED          :
      case MOUSE_EVENT_OUTSIDE_FORM_PRESSED              :
      case MOUSE_EVENT_OUTSIDE_FORM_WHEEL                :
        break;
      //--- The cursor is inside the form, the mouse buttons are not clicked
      case MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED           :  this.MouseInsideNotPressedHandler(id,lparam,dparam,sparam);       break;
      //--- The cursor is inside the form, any mouse button is clicked
      case MOUSE_EVENT_INSIDE_FORM_PRESSED               :  this.MouseInsidePressedHandler(id,lparam,dparam,sparam);          break;
      //--- The cursor is inside the form, the mouse wheel is being scrolled
      case MOUSE_EVENT_INSIDE_FORM_WHEEL                 :  this.MouseInsideWhellHandler(id,lparam,dparam,sparam);            break;
      //--- The cursor is inside the active area, the mouse buttons are not clicked
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED    :  this.MouseActiveAreaNotPressedHandler(id,lparam,dparam,sparam);   break;
      //--- The cursor is inside the active area, any mouse button is clicked
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_PRESSED        :  this.MouseActiveAreaPressedHandler(id,lparam,dparam,sparam);      break;
      //--- The cursor is inside the active area, the mouse wheel is being scrolled
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_WHEEL          :  this.MouseActiveAreaWhellHandler(id,lparam,dparam,sparam);        break;
      //--- The cursor is inside the active area, left mouse button is released
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_RELEASED       :  this.MouseActiveAreaReleasedHandler(id,lparam,dparam,sparam);     break;
      //--- The cursor is within the window scrolling area, the mouse buttons are not clicked
      case MOUSE_EVENT_INSIDE_SCROLL_AREA_NOT_PRESSED    :  this.MouseScrollAreaNotPressedHandler(id,lparam,dparam,sparam);   break;
      //--- The cursor is within the window scrolling area, any mouse button is clicked
      case MOUSE_EVENT_INSIDE_SCROLL_AREA_PRESSED        :  this.MouseScrollAreaPressedHandler(id,lparam,dparam,sparam);      break;
      //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled
      case MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL          :  this.MouseScrollAreaWhellHandler(id,lparam,dparam,sparam);        break;
      //--- The cursor is within the control area, the mouse buttons are not clicked
      case MOUSE_EVENT_INSIDE_CONTROL_AREA_NOT_PRESSED   :  this.MouseControlAreaNotPressedHandler(id,lparam,dparam,sparam);  break;
      //--- The cursor is within the control area, the mouse button (any) is clicked
      case MOUSE_EVENT_INSIDE_CONTROL_AREA_PRESSED       :  this.MouseControlAreaPressedHandler(id,lparam,dparam,sparam);     break;
      //--- The cursor is within the control area, the mouse wheel is being scrolled
      case MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL         :  this.MouseControlAreaWhellHandler(id,lparam,dparam,sparam);       break;
      //--- MOUSE_EVENT_NO_EVENT
      default: break;
     }
   this.m_mouse_event_last=(ENUM_MOUSE_EVENT)id;
  }
//+------------------------------------------------------------------+

Se o identificador de evento passado para o método indica que o cursor está dentro da área de controle, os botões do mouse estão pressionados/não pressionados e o estado da roda, os métodos virtuais correspondentes declarados anteriormente são chamados. A implementação completa desses métodos deve ser feita nas classes filhas. Atualmente, esses métodos não fazem nada, mas sua implementação deve ser realizada nas classes filhas para atender às necessidades específicas:

//+------------------------------------------------------------------+
//| The cursor is inside the control area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CForm::MouseControlAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   return;
  }
//+------------------------------------------------------------------+
//| The cursor is inside the control area,                           |
//| a mouse button is clicked (any)                                  |
//+------------------------------------------------------------------+
void CForm::MouseControlAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   return;
  }
//+------------------------------------------------------------------+
//| The cursor is inside the control area,                           |
//| the mouse wheel is being scrolled                                |
//+------------------------------------------------------------------+
void CForm::MouseControlAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   return;
  }
//+------------------------------------------------------------------+


No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqh da classe de controle TabControl, no método que cria o número especificado de guias, em todas as linhas do método onde o objeto principal é indicado, adicionamos ou alteramos a lógica:

//+------------------------------------------------------------------+
//| Create the specified number of tabs                              |
//+------------------------------------------------------------------+
bool CTabControl::CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="")
  {
//--- Calculate the size and initial coordinates of the tab title
   int w=(tab_w==0 ? this.ItemWidth()  : tab_w);
   int h=(tab_h==0 ? this.ItemHeight() : tab_h);

//--- In the loop by the number of tabs
   CTabHeader *header=NULL;
   CTabField  *field=NULL;
   for(int i=0;i<total;i++)
     {
      //--- Depending on the location of tab titles, set their initial coordinates
      int header_x=2;
      int header_y=2;
      int header_w=w;
      int header_h=h;
      
      //--- Set the current X and Y coordinate depending on the location of the tab headers
      switch(this.Alignment())
        {
         case CANV_ELEMENT_ALIGNMENT_TOP     :
           header_w=w;
           header_h=h;
           header_x=(header==NULL ? 2 : header.RightEdgeRelative());
           header_y=2;
           break;
         case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
           header_w=w;
           header_h=h;
           header_x=(header==NULL ? 2 : header.RightEdgeRelative());
           header_y=this.Height()-header_h-2;
           break;
         case CANV_ELEMENT_ALIGNMENT_LEFT    :
           header_w=h;
           header_h=w;
           header_x=2;
           header_y=(header==NULL ? this.Height()-header_h-2 : header.CoordYRelative()-header_h);
           break;
         case CANV_ELEMENT_ALIGNMENT_RIGHT   :
           header_w=h;
           header_h=w;
           header_x=this.Width()-header_w-2;
           header_y=(header==NULL ? 2 : header.BottomEdgeRelative());
           break;
         default:
           break;
        }
      //--- Create the TabHeader object
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,header_x,header_y,header_w,header_h,clrNONE,255,this.Active(),false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1));
         return false;
        }
      header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,i);
      if(header==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1));
         return false;
        }
      header.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
      header.SetBase(this.GetObject());
      header.SetPageNumber(i);
      header.SetGroup(this.Group()+1);
      header.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true);
      header.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN);
      header.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER);
      header.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true);
      header.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON);
      header.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON);
      header.SetBorderStyle(FRAME_STYLE_SIMPLE);
      header.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
      header.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN);
      header.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER);
      header.SetAlignment(this.Alignment());
      header.SetPadding(this.HeaderPaddingWidth(),this.HeaderPaddingHeight(),this.HeaderPaddingWidth(),this.HeaderPaddingHeight());
      if(header_text!="" && header_text!=NULL)
         this.SetHeaderText(header,header_text+string(i+1));
      else
         this.SetHeaderText(header,"TabPage"+string(i+1));
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT)
         header.SetFontAngle(90);
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT)
         header.SetFontAngle(270);
      header.SetTabSizeMode(this.TabSizeMode());
      
      //--- Save the initial height of the header and set its size in accordance with the header size setting mode
      int h_prev=header_h;
      header.SetSizes(header_w,header_h);
      //--- Get the Y offset of the header position after changing its height and
      //--- shift it by the calculated value only for headers on the left
      int y_shift=header.Height()-h_prev;
      if(header.Move(header.CoordX(),header.CoordY()-(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT ? y_shift : 0)))
        {
         header.SetCoordXRelative(header.CoordX()-this.CoordX());
         header.SetCoordYRelative(header.CoordY()-this.CoordY());
        }
      header.SetVisibleFlag(this.IsVisible(),false);
      //--- In the header, set the pointer to the previous object in the list
      CTabHeader *prev=this.GetTabHeader(i-1);
      header.SetPrevHeader(prev);
      
      //--- Depending on the location of the tab headers, set the initial coordinates of the tab fields
      int field_x=0;
      int field_y=0;
      int field_w=this.Width();
      int field_h=this.Height()-header.Height()-2;
      int header_shift=0;
      
      switch(this.Alignment())
        {
         case CANV_ELEMENT_ALIGNMENT_TOP     :
           field_x=0;
           field_y=header.BottomEdgeRelative();
           field_w=this.Width();
           field_h=this.Height()-header.Height()-2;
           break;
         case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
           field_x=0;
           field_y=0;
           field_w=this.Width();
           field_h=this.Height()-header.Height()-2;
           break;
         case CANV_ELEMENT_ALIGNMENT_LEFT    :
           field_x=header.RightEdgeRelative();
           field_y=0;
           field_h=this.Height();
           field_w=this.Width()-header.Width()-2;
           break;
         case CANV_ELEMENT_ALIGNMENT_RIGHT   :
           field_x=0;
           field_y=0;
           field_h=this.Height();
           field_w=this.Width()-header.Width()-2;
           break;
         default:
           break;
        }
      
      //--- Create the TabField object (tab field)
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,field_x,field_y,field_w,field_h,clrNONE,255,true,false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1));
         return false;
        }
      field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,i);
      if(field==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1));
         return false;
        }
      field.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
      field.SetBase(this.GetObject());
      field.SetPageNumber(i);
      field.SetGroup(this.Group()+1);
      field.SetBorderSizeAll(1);
      field.SetBorderStyle(FRAME_STYLE_SIMPLE);
      field.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true);
      field.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true);
      field.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
      field.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
      field.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
      field.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
      field.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
      field.SetForeColor(CLR_DEF_FORE_COLOR,true);
      field.SetPadding(this.FieldPaddingLeft(),this.FieldPaddingTop(),this.FieldPaddingRight(),this.FieldPaddingBottom());
      field.Hide();
     }
//--- Create the left-right button object
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,this.Width()-32,0,15,15,clrNONE,255,this.Active(),false);
//--- Get the pointer to a newly created object
   CArrowLeftRightBox *box_lr=this.GetArrLeftRightBox();
   if(box_lr!=NULL)
     {
      this.SetVisibleLeftRightBox(false);
      this.SetSizeLeftRightBox(box_lr.Width());
      box_lr.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
      box_lr.SetBase(this.GetObject());
      box_lr.SetID(this.GetMaxIDAll());
      box_lr.SetBorderStyle(FRAME_STYLE_NONE);
      box_lr.SetBackgroundColor(CLR_CANV_NULL,true);
      box_lr.SetOpacity(0);
      box_lr.Hide();
      CArrowLeftButton *lb=box_lr.GetArrowLeftButton();
      if(lb!=NULL)
        {
         lb.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         lb.SetBase(box_lr);
         lb.SetID(this.GetMaxIDAll());
        }
      CArrowRightButton *rb=box_lr.GetArrowRightButton();
      if(rb!=NULL)
        {
         rb.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         rb.SetBase(box_lr);
         rb.SetID(this.GetMaxIDAll());
        }
     }
//--- Create the up-down button object
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,0,this.Height()-32,15,15,clrNONE,255,this.Active(),false);
//--- Get the pointer to a newly created object
   CArrowUpDownBox *box_ud=this.GetArrUpDownBox();
   if(box_ud!=NULL)
     {
      this.SetVisibleUpDownBox(false);
      this.SetSizeUpDownBox(box_ud.Height());
      box_ud.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
      box_ud.SetBase(this.GetObject());
      box_ud.SetID(this.GetMaxIDAll());
      box_ud.SetBorderStyle(FRAME_STYLE_NONE);
      box_ud.SetBackgroundColor(CLR_CANV_NULL,true);
      box_ud.SetOpacity(0);
      box_ud.Hide();
      CArrowDownButton *db=box_ud.GetArrowDownButton();
      if(db!=NULL)
        {
         db.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         db.SetBase(box_ud);
         db.SetID(this.GetMaxIDAll());
        }
      CArrowUpButton *ub=box_ud.GetArrowUpButton();
      if(ub!=NULL)
        {
         ub.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         ub.SetBase(box_ud);
         ub.SetID(this.GetMaxIDAll());
        }
     }
//--- Arrange all titles in accordance with the specified display modes and select the specified tab
   this.ArrangeTabHeaders();
   this.Select(selected_page,true);
   return true;
  }
//+------------------------------------------------------------------+

Embora seja longo, o método está completo para que possa ser compreendida sua lógica e quais objetos são indicados como base e principal através dos ponteiros.

Em vez de simplesmente especificar o objeto principal

SetMain(this.GetMain());

agora verificamos se o objeto principal é o atual e, se for, inserimos um ponteiro para ele, caso contrário, inserimos um ponteiro para o objeto principal:

SetMain(this.IsMain() ? this.GetObject() : this.GetMain());


Se os títulos da guia em um controle TabControl estiverem em uma linha e não caberem no contêiner, eles podem ser rolados usando os botões de seta. O título selecionado é sempre maior do que o não selecionado em dois pixels de cada lado. Se você rolar a fileira de títulos de tal forma que o título selecionado ultrapasse a borda esquerda do contêiner, uma pequena parte desse título poderá ser vista.

Isso ocorre porque o tamanho do título selecionado é sempre maior do que o não selecionado, e qualquer título que ultrapasse a borda do título selecionado é cortado para o tamanho do título não selecionado. Para evitar isso, é necessário ajustar o limite da área visível do contêiner de forma que, se o cabeçalho selecionado ultrapassar a borda, ele seja cortado um pouco mais do que o não selecionado.

No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\TabHeader.mqh da classe cabeçalho da guia, finalizaremos o método que recorta a imagem contornada pela área de visibilidade retangular calculada. Reduziremos em dois pixels o tamanho usado para recortar os títulos quando os botões de seta para cima e para baixo estiverem visíveis, pois o espaço entre o cabeçalho e os botões é muito grande e não parece arrumado. Ajustaremos também a coordenada da área de visibilidade do contêiner para o cabeçalho da guia selecionada que ultrapassou a borda:

//+------------------------------------------------------------------+
//| Crop the image outlined by the calculated                        |
//| rectangular visibility scope                                     |
//+------------------------------------------------------------------+
void CTabHeader::Crop(void)
  {
//--- Get the pointer to the base object
   CGCnvElement *base=this.GetBase();
//--- If the object does not have a base object it is attached to, then there is no need to crop the hidden areas - leave
   if(base==NULL)
      return;
//--- Set the initial coordinates and size of the visibility scope to the entire object
   int vis_x=0;
   int vis_y=0;
   int vis_w=this.Width();
   int vis_h=this.Height();
//--- Set the size of the top, bottom, left and right areas that go beyond the container
   int crop_top=0;
   int crop_bottom=0;
   int crop_left=0;
   int crop_right=0;
//--- Get the additional size, by which to crop the titles when the arrow buttons are visible
   int add_size_lr=(this.IsVisibleLeftRightBox() ? this.m_arr_butt_lr_size : 0);
   int add_size_ud=(this.IsVisibleUpDownBox()    ? this.m_arr_butt_ud_size-2 : 0);
   int correct_size_vis=(this.State() ? 0 : 2);
//--- Calculate the boundaries of the container area, inside which the object is fully visible
   int top=fmax(base.CoordY()+(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_TOP),base.CoordYVisibleArea())+correct_size_vis+(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT ? add_size_ud : 0);
   int bottom=fmin(base.BottomEdge()-(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_BOTTOM),base.BottomEdgeVisibleArea()+1)-correct_size_vis-(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT ? add_size_ud : 0);
   int left=fmax(base.CoordX()+(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_LEFT),base.CoordXVisibleArea())+correct_size_vis;
   int right=fmin(base.RightEdge()-(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_RIGHT),base.RightEdgeVisibleArea()+1)-add_size_lr;

//--- Adjust the coordinate of the visible area if the selected tab header has gone beyond the left or bottom edge of the area
   if(this.State())
     {
      if((this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP || this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM) && this.CoordX()<left)
         left+=4;
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT && this.BottomEdge()>bottom)
         bottom-=4;
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT && this.CoordY()<top)
         top+=4;
     }

//--- Calculate the values of the top, bottom, left and right areas, at which the object goes beyond
//--- the boundaries of the container area, inside which the object is fully visible
   crop_top=this.CoordY()-top;
   if(crop_top<0)
      vis_y=-crop_top;
   crop_bottom=bottom-this.BottomEdge()-1;
   if(crop_bottom<0)
      vis_h=this.Height()+crop_bottom-vis_y;
   crop_left=this.CoordX()-left;
   if(crop_left<0)
      vis_x=-crop_left;
   crop_right=right-this.RightEdge()-1;
   if(crop_right<0)
      vis_w=this.Width()+crop_right-vis_x;
//--- If there are areas that need to be hidden, call the cropping method with the calculated size of the object visibility scope
   if(crop_top<0 || crop_bottom<0 || crop_left<0 || crop_right<0)
      this.Crop(vis_x,vis_y,vis_w,vis_h);
  }
//+------------------------------------------------------------------+

Após as melhorias, o espaço entre os cabeçalhos colocados verticalmente (esquerda/direita) e seus botões de rolagem ficará mais limpo e com uma aparência melhor. Além disso, se o cabeçalho selecionado ultrapassar a borda esquerda ou inferior do contêiner, não será mostrado parcialmente quando a forma for movida.

Mas há outro problema: se você observar atentamente a imagem acima, verá que, depois de mover o título selecionado para fora da borda, uma margem branca permanece abaixo. Isso ocorre porque a moldura do campo pertencente ao cabeçalho que ultrapassou a borda não é desenhada até a borda. Isso acontece porque, visualmente, o título e o campo da guia devem ser semelhantes. Isso é conseguido desenhando primeiro uma moldura no campo e, em seguida, uma linha com a cor do campo no local adjacente ao campo do título. Assim, a linha visível entre o título e o campo é apagada. Quando o cabeçalho sai da borda, parte dessa "fusão do cabeçalho com o campo" ainda é visível.

Para resolver esse problema, precisamos na classe do objeto campo de guia no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\TabField.mqh, controlar a localização do cabeçalho no método que desenha a moldura do elemento. Se o título for selecionado e estiver fora da borda, não será necessário desenhar uma linha que mescle visualmente o título com o campo:

//+------------------------------------------------------------------+
//| Draw the element frame depending on the header position          |
//+------------------------------------------------------------------+
void CTabField::DrawFrame(void)
  {
//--- Set the initial coordinates
   int x1=0;
   int y1=0;
   int x2=this.Width()-1;
   int y2=this.Height()-1;
//--- Get the tab header corresponding to the field
   CTabHeader *header=this.GetHeaderObj();
   if(header==NULL)
      return;
//--- Draw a rectangle that completely outlines the field
   this.DrawRectangle(x1,y1,x2,y2,this.BorderColor(),this.Opacity());
//--- Depending on the location of the header, draw a line on the edge adjacent to the header.
//--- The line size is calculated from the heading size and corresponds to it with a one-pixel indent on each side
//--- thus, visually the edge will not be drawn on the adjacent side of the header
   switch(header.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :
        if(header.State() && header.CoordX()<this.CoordX())
           return;
        this.DrawLine(header.CoordXRelative()+1,0,header.RightEdgeRelative()-2,0,this.BackgroundColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
        if(header.State() && header.CoordX()<this.CoordX())
           return;
        this.DrawLine(header.CoordXRelative()+1,this.Height()-1,header.RightEdgeRelative()-2,this.Height()-1,this.BackgroundColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :
        if(header.State() && header.BottomEdge()>this.BottomEdge())
           return;
        this.DrawLine(0,header.BottomEdgeRelative()-2,0,header.CoordYRelative()+1,this.BackgroundColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :
        if(header.State() && header.CoordY()<this.CoordY())
           return;
        this.DrawLine(this.Width()-1,header.BottomEdgeRelative()-2,this.Width()-1,header.CoordYRelative()+1,this.BackgroundColor(),this.Opacity());
        break;
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Após essas melhorias no cabeçalho da guia e nos objetos do campo da guia, todos os defeitos visuais ao rolar a fileira de títulos serão corrigidos.


Além disso, vamos mudar a lógica de interação entre o mouse e o separador na classe do objeto do controle CplitContainer.

Quando o cursor do mouse entrar na área de controle do objeto (na área do separador), primeiro no local onde o objeto separador deve aparecer e de maneira simples, na área de controle, desenharemos um retângulo pontilhado, semelhante ao controle SplitContainer no MS Visual Studio.

Assim que mantivermos o botão do mouse pressionado na área delimitada por esse retângulo, um objeto separador aparecerá e já poderemos movê-lo, alterando o tamanho dos painéis. Quando a movimentação estiver concluída, o objeto separador ficará oculto e o retângulo pontilhado será apagado.

Esse comportamento não é exatamente igual ao do separador no MS Visual Studio, mas é melhor e não faz com que o objeto separador hachurado apareça constantemente, substituindo-o por um retângulo pontilhado discreto que indica a área de interação.

No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\SplitContainer.mqh, na seção pública da classe, declararemos dois métodos para desenhar retângulos vazios e pontilhados:

//--- (1) set and (2) return the panel that does not change its size when the container is resized
   void              SetFixedPanel(const ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL value)
                       { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL,value);                                                       }
   ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL FixedPanel(void) const
                       { return(ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL);        }
   
   //--- Draw an (1) empty and (2) dotted rectangle
   virtual void      DrawRectangleEmpty(void);
   virtual void      DrawRectangleDotted(void);
   
//--- Create a new attached element on the specified panel

O método que desenha o retângulo tracejado desenha o retângulo correspondente na área de interação, enquanto o retângulo vazio simplesmente apaga o tracejado desenhado anteriormente.

Vamos declarar dois manipuladores de eventos, um para processar o cursor na área de controle e outro para processar o botão pressionado do mouse na mesma área:

//--- 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);
//--- 'The cursor is inside the control area, no mouse buttons are clicked' event handler
   virtual void      MouseControlAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the control area, a mouse button is clicked (any)' event handler
   virtual void      MouseControlAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Last mouse event handler
   virtual void      OnMouseEventPostProcessing(void);


No método que cria painéis, para cada um dos painéis criados e o objeto separador, especificamos os objetos principal e base:

//+------------------------------------------------------------------+
//| Create the panels                                                |
//+------------------------------------------------------------------+
void CSplitContainer::CreatePanels(void)
  {
   this.m_list_elements.Clear();
   if(this.SetsPanelParams())
     {
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,this.m_panel1_x,this.m_panel1_y,this.m_panel1_w,this.m_panel1_h,clrNONE,255,true,false))
         return;
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,this.m_panel2_x,this.m_panel2_y,this.m_panel2_w,this.m_panel2_h,clrNONE,255,true,false))
         return;
      for(int i=0;i<2;i++)
        {
         CSplitContainerPanel *panel=this.GetPanel(i);
         if(panel==NULL)
            continue;
         panel.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         panel.SetBase(this.GetObject());
        }
      //---
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLITTER,this.m_splitter_x,this.m_splitter_y,this.m_splitter_w,this.m_splitter_h,clrNONE,255,true,false))
         return;
      CSplitter *splitter=this.GetSplitter();
      if(splitter!=NULL)
        {
         splitter.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         splitter.SetBase(this.GetObject());
         splitter.SetMovable(true);
         splitter.SetDisplayed(false);
         splitter.Hide();
        }
     }
  }
//+------------------------------------------------------------------+


No método que define os parâmetros dos painéis, bem no final dele, definimos coordenadas e tamanhos da área de controle iguais às propriedades definidas para o separador.

//+------------------------------------------------------------------+
//| Set the panel parameters                                         |
//+------------------------------------------------------------------+
bool CSplitContainer::SetsPanelParams(void)
  {
   switch(this.SplitterOrientation())
     {

      //---...
      //---...

     }
//--- Set the coordinates and sizes of the control area equal to the properties set by the separator
   this.SetControlAreaX(this.m_splitter_x);
   this.SetControlAreaY(this.m_splitter_y);
   this.SetControlAreaWidth(this.m_splitter_w);
   this.SetControlAreaHeight(this.m_splitter_h);
   return true;
  }
//+------------------------------------------------------------------+

Anteriormente, nós os definimos escrevendo propriedades usando os métodos SetProperty(), que é basicamente a mesma coisa. Mas faz mais sentido, na minha opinião.


No manipulador de eventos, após calcular as coordenadas e dimensões do separador e antes de deslocar o objeto separador para as coordenadas especificadas, apagamos o retângulo pontilhado desenhado anteriormente:

//+------------------------------------------------------------------+
//| 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;
        }
      //--- Draw an empty rectangle
      this.DrawRectangleEmpty();
      //--- 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);
        }
     }
  }
//+------------------------------------------------------------------+

Sempre temos um retângulo pontilhado quando passamos o mouse sobre a área de separação. Ao pressionarmos o botão do mouse na área de controle delimitada por este retângulo, um objeto separador aparecerá, que será capturado pelo mouse e movido. Antes de movê-lo, precisamos apagar a área pontilhada desenhada, o que é feito pelo método que desenha um retângulo vazio neste local.


Manipulador de evento "Cursor dentro da área de controle, botões do mouse não pressionados":

//+------------------------------------------------------------------+
//| The cursor is inside the control area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CSplitContainer::MouseControlAreaNotPressedHandler(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- If the separator is non-movable, leave
   if(this.SplitterFixed())
      return;
//--- Draw an empty rectangle in the control area
   this.DrawRectangleEmpty();
//--- Draw a dotted rectangle in the control area
   this.DrawRectangleDotted();
//--- 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 and show it
      splitter.SetDisplayed(true);
      splitter.Erase(true);
      splitter.Show();
     }
  }
//+------------------------------------------------------------------+

Assim que o ponteiro do mouse é posicionado na área de controle, ocorre um evento correspondente que é enviado para o manipulador de eventos do objeto forma. Este manipulador de eventos virtuais, cuja implementação é mostrada aqui, já é chamado a partir deste manipulador de eventos. Se houver um separador fixo, nenhuma ação é necessária, e saímos do manipulador. Em seguida, é necessário limpar o espaço na área do separador e desenhar um retângulo pontilhado nele. Se o objeto separador tiver um sinalizador oculto, ele deve ser removido e o objeto tem de ser limpo e exibido. Isso permite que o objeto separador fique invisível, mas ainda possa ser clicado. O clique no controle SplitContainer fará com que se clique no objeto separador, não no substrato do controle SplitContainer. Isso prepara o objeto para ser movido e desenha um retângulo tracejado no substrato do objeto SplitContainer, delimitando a área de controle.


Manipulador de evento "Cursor dentro da área de controle, botão do mouse pressionado (qualquer)":

//+------------------------------------------------------------------+
//| The cursor is inside the control area,                           |
//| a mouse button is clicked (any)                                  |
//+------------------------------------------------------------------+
void CSplitContainer::MouseControlAreaPressedHandler(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- If the separator is non-movable, leave
   if(this.SplitterFixed())
      return;
//--- Draw an empty rectangle in the control area
   this.DrawRectangleEmpty();
  }
//+------------------------------------------------------------------+

Assim que o botão do mouse é pressionado, esse manipulador é chamado. Com um separador fixo, simplesmente saímos do manipulador. Caso contrário, apagamos o retângulo pontilhado desenhado anteriormente.


No manipulador do último evento do mouse, substituiremos os nomes das constantes de estado e os eventos do mouse renomeados anteriormente e apagaremos o retângulo pontilhado desenhado anteriormente:

//+------------------------------------------------------------------+
//| 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_CONTROL_AREA_NOT_PRESSED ||
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_CONTROL_AREA_PRESSED     ||
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL       ||
           this.MouseEventLast()==MOUSE_EVENT_NO_EVENT)
          {
            //--- Draw an empty rectangle in the control area
            this.DrawRectangleEmpty();
            //--- 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_CONTROL_AREA_NOT_PRESSED:
      case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED    :
      case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL      :
        break;
      //--- MOUSE_EVENT_NO_EVENT
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Cada objeto gráfico possui um manipulador para o último evento do mouse. O manipulador é chamado quando o cursor sai da área do objeto. Este manipulador apaga o retângulo pontilhado delineando a área de controle caso o cursor do mouse estivesse anteriormente na área de todo o controle SplitContainer, ou na área de seu separador (na área de controle).


Método que desenha um retângulo vazio:

//+------------------------------------------------------------------+
//| Draw an empty rectangle                                          |
//+------------------------------------------------------------------+
void CSplitContainer::DrawRectangleEmpty(void)
  {
   int cx1=this.ControlAreaLeftRelative();
   int cx2=this.ControlAreaRightRelative();
   int cy1=this.ControlAreaTopRelative();
   int cy2=this.ControlAreaBottomRelative();
   this.DrawRectangleFill(cx1,cy1,cx2,cy2,CLR_CANV_NULL,0);
   this.Update();
  }
//+------------------------------------------------------------------+

Obtemos as coordenadas relativas da localização do retângulo, largura e altura dentro do controle SplitContainer e desenhamos um retângulo pintado de cores transparentes.


Método que desenha um retângulo pontilhado:

//+------------------------------------------------------------------+
//| Draw a dotted rectangle                                          |
//+------------------------------------------------------------------+
void CSplitContainer::DrawRectangleDotted(void)
  {
   int shift=0;
   int cx1=this.ControlAreaLeftRelative();
   int cx2=fmin(this.ControlAreaRightRelative(),this.VisibleAreaWidth()+2);
   int cy1=this.ControlAreaTopRelative();
   int cy2=this.ControlAreaBottomRelative();
//--- Draw points in the next-but-one fashion along the upper border of the rectangle from left to right
   for(int x=cx1+1;x<cx2-2;x+=2)
      this.SetPixel(x,cy1,this.ForeColor(),255);
//--- Get the offset of the next point depending on where the last point was placed
   shift=((cx2-cx1-2) %2==0 ? 0 : 1);
//--- Draw points in the next-but-one fashion along the right border of the rectangle from top to bottom
   for(int y=cy1+1+shift;y<cy2-2;y+=2)
      this.SetPixel(cx2-2,y,this.ForeColor(),255);
//--- Get the offset of the next point depending on where the last point was placed
   shift=(this.ControlAreaHeight()-2 %2==0 ? 1 : 0);
//--- Draw points in the next-but-one fashion along the lower border of the rectangle from right to left
   for(int x=cx2-2-shift;x>cx1;x-=2)
      this.SetPixel(x,cy2-2,this.ForeColor(),255);
//--- Get the offset of the next point depending on where the last point was placed
   shift=((cx2-cx1-2) %2==0 ? 0 : 1);
//--- Draw points in the next-but-one fashion along the left border of the rectangle from bottom to top
   for(int y=cy2-2-shift;y>cy1;y-=2)
      this.SetPixel(cx1+1,y,this.ForeColor(),255);
//--- Update the canvas
   this.Update();
  }
//+------------------------------------------------------------------+

O método utiliza uma lógica descrita nos comentários do código. Em resumo, é necessário desenhar uma linha com pontos distribuídos ao longo dela. Isso é realizado em quatro etapas: da esquerda para a direita, de cima para baixo, da direita para a esquerda e de baixo para cima. O incremento do índice de ciclo é de dois, o que permite que um ponto seja adicionado a cada iteração e o índice de ciclo serve como coordenada. No entanto, há uma particularidade a ser observada: se um ponto foi definido no final do ciclo, o próximo ciclo não deve começar com um ponto. Para contornar isso, é necessário calcular a largura e altura do retângulo e, dependendo do valor resultante, adicionar 1 ou 0 à próxima coordenada. No ciclo inverso, o incremento é subtraído da coordenada do ponto. Dessa forma, é possível obter um retângulo pontilhado com pontos sempre distribuídos ao longo dele. Outra opção seria desenhar um retângulo suavizado, com o método DrawPolygonAA(), que permite definir o tipo de linha a ser desenhada. No entanto, neste caso, infelizmente, o tipo de linha STYLE_DOT desenha segmentos maiores que um pixel, o que não é adequado para o objetivo deste método.


Assim que o cursor sair da área do separador do controle SplitContainer, ele será direcionado para uma das áreas dos painéis do controle ou até mesmo para fora dele. Se o cursor ultrapassar o limite do controle, o manipulador do evento de mouse mencionado anteriormente será acionado. Se o cursor estiver sobre um dos painéis do controle SplitContainer, será necessário lidar com a saída do cursor da área do controle base - removendo o retângulo pontilhado - no manipulador de eventos deste painel.

No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\SplitContainerPanel.mqh, no manipulador de eventos "Cursor dentro da área ativa, botões do mouse não pressionados", adicionamos uma linha que desenha um retângulo vazio no área de controle do objeto base (o objeto base para o painel é um contêiner de controle SplitContainer):

//+------------------------------------------------------------------+
//| '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;
//--- Draw an empty rectangle in the base object control area
   base.DrawRectangleEmpty();
//--- 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();
     }
  }
//+------------------------------------------------------------------+

Assim que o cursor entra no painel, esse manipulador é acionado e apaga o retângulo pontilhado que contorna a área de controle.


Vamos apurar o objeto separador auxiliar no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\Splitter.mqh.

Após uma atualização recente, ao compilar a biblioteca, apareceu o aviso:

deprecated behavior, hidden method calling will be disabled in a future MQL compiler version    SplitContainer.mqh      758     16

Uma vez no endereço indicado no log, chegamos a esta linha no SplitContainer.mqh:

//+------------------------------------------------------------------+
//| The cursor is inside the control area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CSplitContainer::MouseControlAreaNotPressedHandler(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- If the separator is non-movable, leave
   if(this.SplitterFixed())
      return;
//--- Draw an empty rectangle in the control area
   this.DrawRectangleEmpty();
//--- Draw a dotted rectangle in the control area
   this.DrawRectangleDotted();
//--- 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 and show it
      splitter.SetDisplayed(true);
      splitter.Erase(true);
      splitter.Show();
     }
  }
//+------------------------------------------------------------------+

Este é um método virtual que limpa completamente o fundo do objeto gráfico.

Existe exatamente o mesmo método em \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh:

//+------------------------------------------------------------------+
//| Clear the element completely                                     |
//+------------------------------------------------------------------+
void CWinFormBase::Erase(const bool redraw=false)
  {
//--- Fully clear the element with the redrawing flag
   CGCnvElement::Erase(redraw);
  }
//+------------------------------------------------------------------+

e em \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh:

//+------------------------------------------------------------------+
//| Clear the element completely                                     |
//+------------------------------------------------------------------+
void CGCnvElement::Erase(const bool redraw=false)
  {
   this.m_canvas.Erase(CLR_CANV_NULL);
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

A assinatura dos métodos é idêntica, e no final tudo leva ao método Erase() do objeto elemento gráfico CGCnvElement. Por essa razão, não consigo entender por que o compilador vê ambiguidade. Mas vamos consertar. Vamos adicionar o método Erase() ao arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\Splitter.mqh. E vamos declarar dois manipuladores de eventos de mouse:

//--- Redraw the object
   virtual void      Redraw(bool redraw);
//--- Clear the element filling it with color and opacity
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Clear the element with a gradient fill
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
//--- Clear the element completely
   virtual void      Erase(const bool redraw=false) { CWinFormBase::Erase(redraw);  }
//--- 'The cursor is inside the active area, a mouse button is clicked (any)' event handler
   virtual void      MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the active area, the left mouse button is clicked' event handler
   virtual void      MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
  };
//+------------------------------------------------------------------+

O método Erase() simplesmente chama o mesmo método de classe pai, o que nos salva do aviso do compilador.

No método que desenha a grade, adicionamos transparência (ao invés do valor 255 vamos inserir 200), o que deixará o objeto separador um pouco transparente:

//+------------------------------------------------------------------+
//| Draw the grid                                                    |
//+------------------------------------------------------------------+
void CSplitter::DrawGrid(void)
  {
   for(int y=0;y<this.Height()-1;y++)
      for(int x=0;x<this.Width();x++)
         this.SetPixel(x,y,this.ForeColor(),uchar(y%2==0 ? (x%2==0 ? 200 : 0) : (x%2==0 ? 0 : 200)));
  }
//+------------------------------------------------------------------+

Os pontos agora serão desenhados com uma opacidade de 200, o que os tornará levemente transparentes e melhorará um pouco a aparência do separador.


Manipulador de evento "Cursor dentro da área ativa, botão do mouse pressionado (qualquer)":

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| a mouse button is clicked (any)                                  |
//+------------------------------------------------------------------+
void CSplitter::MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- If the separator is not displayed
   if(!this.Displayed())
     {
      //--- Enable the display of the separator and show it
      this.SetDisplayed(true);
      this.Show();
     }
//--- Redraw the separator
   this.Redraw(true);
  }
//+------------------------------------------------------------------+

O manipulador é acionado quando o botão do mouse é pressionado no objeto. Se o objeto ainda não estiver exibido, ativamos sua exibição.
Em seguida, redesenhamos o objeto, que exibirá um retângulo hachurado em seu plano de fundo, exibindo totalmente o objeto separador.


Manipulador de evento "Cursor dentro da área ativa, botão do mouse liberado (esquerdo)":

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| left mouse button released                                       |
//+------------------------------------------------------------------+
void CSplitter::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   this.SetDisplayed(false);
   this.Hide();
   ::ChartRedraw(this.ChartID());
  }
//+------------------------------------------------------------------+

Ao soltar o botão do mouse dentro de um objeto gráfico, é chamado o manipulador de eventos. Neste caso, definimos que o objeto separador não deve ser exibido e o escondemos. Para visualizar as alterações imediatamente, redesenhamos o gráfico. Assim, se o objeto separador for movido para um novo local e o botão do mouse for solto, ele será ocultado, cumprindo sua finalidade de mover a área separadora do controle SplitContainer.


Vamos alterar a classe coleção de objetos gráficos no arquivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh.

Neste caso, precisamos tratar a situação em que o cursor do mouse está sobre uma parte oculta de um objeto gráfico anexado a um controle. Isso pode acontecer quando parte do objeto está além da área do objeto pai. Nessa situação, o objeto gráfico fica invisível nessa área e não deve reagir ao cursor. Além disso, ao clicar em um elemento anexado a um painel, é necessário trazer primeiro o painel inteiro e todos os objetos anexados a ele para o primeiro plano, seguido pelo próprio objeto clicado com o mouse.

No método que encontra os objetos de interação, vamos adicionar processamento de interação com áreas ocultas de objetos, uma vez que tais objetos devem ser ignorados:

//+------------------------------------------------------------------+
//| Search for interaction objects                                   |
//+------------------------------------------------------------------+
CForm *CGraphElementsCollection::SearchInteractObj(CForm *form,const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- If a non-empty pointer is passed
   if(form!=NULL)
     {
      //--- Create the list of interaction objects
      int total=form.CreateListInteractObj();
      //--- In the loop by the created list
      for(int i=total-1;i>WRONG_VALUE;i--)
        {
         //--- get the next form object
         CForm *obj=form.GetInteractForm(i);
         //--- If the object is received, but is not visible, or not active, or should not be displayed, skip it
         if(obj==NULL || !obj.IsVisible() || !obj.Enabled() || !obj.Displayed())
            continue;
         
         //--- If the form object is TabControl, return the selected tab under the cursor
         if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)
           {
            CTabControl *tab_ctrl=obj;
            CForm *elm=tab_ctrl.SelectedTabPage();
            if(elm!=NULL && elm.MouseFormState(id,lparam,dparam,sparam)>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
               return elm;
           }
         
         //--- If the form object is a SplitContainer control or a panel of the SplitContainer control,
         //--- and if the cursor is located on the area protruding beyond the panel edges, then skip such an object
         if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL)
           {
            if(!obj.CursorInsideVisibleArea(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
               continue;
           }
         
         //--- If the form object is attached to the panel of the SplitContainer control
         //--- and if the object goes beyond the edges of the panel, and the cursor is on the area protruding beyond the edges of the panel, then skip such an object
         CForm *base=obj.GetBase();
         if(base!=NULL && base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL)
           {
            if(!obj.CursorInsideVisibleArea(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
               continue;
           }
         
         //--- If the mouse cursor is over the object, return the pointer to this object
         if(obj.MouseFormState(id,lparam,dparam,sparam)>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
            return obj;
        }
     }
//--- Return the same pointer
   return form;
  }
//+------------------------------------------------------------------+

Atualmente, apenas os elementos gráficos com interação incorreta com o mouse são processados. Em seguida, implementaremos a lógica correta para o processamento de cada elemento gráfico individualmente. É importante notar que aplicar o mesmo processamento aos cabeçalhos de guia do controle TabControl pode causar problemas na funcionalidade de rolagem. Por esse motivo, ainda não estamos aplicando o processamento universal em todos os controles, mas sim entendendo as necessidades específicas de cada um para garantir a correta funcionalidade.

Foi identificado e corrigido um comportamento incorreto dos objetos de interação no método que retorna o ponteiro para a forma sob o cursor devido a problemas com a "perda de estado" do mouse. Para evitar esse problema, adicionamos uma verificação no estado do mouse antes de retornar o ponteiro para o objeto encontrado. Se o sinalizador de exibição do objeto tiver sido redefinido, o processamento desse objeto será pulado.

//+------------------------------------------------------------------+
//| Return the pointer to the form located under the cursor          |
//+------------------------------------------------------------------+
CForm *CGraphElementsCollection::GetFormUnderCursor(const int id, 
                                                    const long &lparam, 
                                                    const double &dparam, 
                                                    const string &sparam,
                                                    ENUM_MOUSE_FORM_STATE &mouse_state,
                                                    long &obj_ext_id,
                                                    int &form_index)
  {
//--- Set the ID of the extended standard graphical object to -1 
//--- and the index of the anchor point managed by the form to -1
   obj_ext_id=WRONG_VALUE;
   form_index=WRONG_VALUE;
//--- Initialize the mouse status relative to the form
   mouse_state=MOUSE_FORM_STATE_NONE;
//--- Declare the pointers to graphical element collection class objects
   CGCnvElement *elm=NULL;
   CForm *form=NULL;
//--- Get the list of objects the interaction flag is set for (there should be only one object)
   CArrayObj *list=CSelect::ByGraphCanvElementProperty(GetListCanvElm(),CANV_ELEMENT_PROP_INTERACTION,true,EQUAL);
//--- If managed to obtain the list and it is not empty,
   if(list!=NULL && list.Total()>0)
     {
      //--- Get the only graphical element there
      elm=list.At(0);
      //--- If the element is a form object or its descendants
      if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE && elm.IsVisible())
        {
         //--- Assign the pointer to the element for the form object pointer
         form=elm;
         //--- Get the mouse status relative to the form
         mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
         //--- If the cursor is inside the form,
         if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
           {
            //--- Find the interaction object.
            //--- This will be either the found object or the same form
            form=this.SearchInteractObj(form,id,lparam,dparam,sparam);
            //--- Get the mouse status of the found object
            mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
            //--- Return the form object
            //Comment(form.TypeElementDescription()," ",form.Name(),", ZOrder: ",form.Zorder(),", Interaction: ",form.Interaction());
            return form;
           }
        }
     }
//--- If there is no a single form object with a specified interaction flag,
//--- in the loop by all graphical element collection class objects
   int total=this.m_list_all_canv_elm_obj.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the next element
      elm=this.m_list_all_canv_elm_obj.At(i);
      if(elm==NULL || !elm.IsVisible() || !elm.Enabled() || !elm.Displayed())
         continue;
      //--- if the obtained element is a form object or its descendants
      if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE)
        {
         //--- Assign the pointer to the element for the form object pointer
         form=elm;
         //--- Get the mouse status relative to the form
         mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
         //--- If the cursor is within the form, return the pointer to the form
         if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
           {
            //--- Find the interaction object.
            //--- This will be either the found object or the same form
            form=this.SearchInteractObj(form,id,lparam,dparam,sparam);
            //--- Get the mouse status of the found object
            mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
            //--- Return the form object
            //Comment(form.TypeElementDescription()," ",form.Name(),", ZOrder: ",form.Zorder(),", Interaction: ",form.Interaction());
            return form;
           }
        }
     }
//--- If there is no a single form object from the collection list
//--- Get the list of extended standard graphical objects
   list=this.GetListStdGraphObjectExt();
   if(list!=NULL)
     {
      //--- in the loop by all extended standard graphical objects
      for(int i=0;i<list.Total();i++)
        {
         //--- get the next graphical object,
         CGStdGraphObj *obj_ext=list.At(i);
         if(obj_ext==NULL)
            continue;
         //--- get the object of its toolkit,
         CGStdGraphObjExtToolkit *toolkit=obj_ext.GetExtToolkit();
         if(toolkit==NULL)
            continue;
         //--- handle the event of changing the chart for the current graphical object
         obj_ext.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam);
         //--- Get the total number of form objects created for the current graphical object
         total=toolkit.GetNumControlPointForms();
         //--- In the loop by all form objects
         for(int j=0;j<total;j++)
           {
            //--- get the next form object,
            form=toolkit.GetControlPointForm(j);
            if(form==NULL)
               continue;
            //--- get the mouse status relative to the form
            mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
            //--- If the cursor is inside the form,
            if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
              {
               //--- set the object ID and form index
               //--- and return the pointer to the form
               obj_ext_id=obj_ext.ObjectID();
               form_index=j;
               return form;
              }
           }
        }
     }
//--- Nothing is found - return NULL
   .return NULL;
  }
//+------------------------------------------------------------------+


No manipulador de eventos dentro da forma, quando o botão do mouse é pressionado, adicionamos uma exibição preliminar da forma inteira no primeiro plano. Em seguida, após chamar o manipulador de eventos do objeto forma, redesenhamos o gráfico para exibir as mudanças imediatamente:

            //+---------------------------------------------------------------------------------------------+
            //| 'The cursor is inside the form, a mouse button is clicked (any)' event handler              |
            //+---------------------------------------------------------------------------------------------+
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_PRESSED)
              {
               this.SetChartTools(::ChartID(),false);
               //--- If the flag of holding the form is not set yet
               if(!pressed_form)
                 {
                  pressed_form=true;      // set the flag of pressing on the form
                  pressed_chart=false;    // disable the flag of pressing on the form
                 }
               CForm *main=form.GetMain();
               if(main!=NULL)
                  main.BringToTop();
               form.OnMouseEvent(MOUSE_EVENT_INSIDE_FORM_PRESSED,lparam,dparam,sparam);
               ::ChartRedraw(form.ChartID());
              }
            //+---------------------------------------------------------------------------------------------+

Aqui: obtemos um ponteiro para o objeto principal e, se recebido, mostramos em primeiro plano o objeto com todos os elementos anexados a ele. Em seguida , chamamos o manipulador de eventos do objeto forma com o qual estamos interagindo e, ao final, atualizamos o gráfico.


Faremos melhorias semelhantes no bloco de processamento do cursor dentro da área ativa quando o botão do mouse for pressionado:

            //+---------------------------------------------------------------------------------------------+
            //| 'The cursor is inside the active area,  any mouse button is clicked' event handler          |
            //+---------------------------------------------------------------------------------------------+
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED && !move)
              {
               pressed_form=true;                                       // the flag of holding the mouse button on the form
               //--- If the left mouse button is pressed
               if(this.m_mouse.IsPressedButtonLeft())
                 {
                  //--- Set flags and form parameters
                  move=true;                                            // movement flag
                  form.SetInteraction(true);                            // flag of the form interaction with the environment
                  CForm *main=form.GetMain();
                  if(main!=NULL)
                     main.BringToTop();
                  form.BringToTop();                                    // form on the background - above all others
                  form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX()); // Cursor shift relative to the X coordinate
                  form.SetOffsetY(this.m_mouse.CoordY()-form.CoordY()); // Cursor shift relative to the Y coordinate
                  this.ResetAllInteractionExeptOne(form);               // Reset interaction flags for all forms except the current one
                  
                  //--- Get the maximum ZOrder
                  long zmax=this.GetZOrderMax();
                  //--- If the maximum ZOrder has been received and the form's ZOrder is less than the maximum one or the maximum ZOrder of all forms is equal to zero
                  if(zmax>WRONG_VALUE && (form.Zorder()<zmax || zmax==0))
                    {
                     //--- If the form is not a control point for managing an extended standard graphical object,
                     //--- set the form's ZOrder above all others
                     if(form.Type()!=OBJECT_DE_TYPE_GFORM_CONTROL)
                        this.SetZOrderMAX(form);
                    }
                 }
               form.OnMouseEvent(MOUSE_EVENT_INSIDE_ACTIVE_AREA_PRESSED,lparam,dparam,sparam);
               ::ChartRedraw(form.ChartID());
              }
            //+---------------------------------------------------------------------------------------------+


E adicionamos três novos blocos para lidar com novos eventos de cursor do mouse dentro da área de controle do objeto forma:

            //+--------------------------------------------------------------------------------------------------+
            //| 'The cursor is inside the window scrolling area, the mouse wheel is being scrolled' event handler|
            //+--------------------------------------------------------------------------------------------------+
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL)
              {
               form.OnMouseEvent(MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL,lparam,dparam,sparam);
              }
            //+-------------------------------------------------------------------------------------------------+
            //| 'The cursor is inside the control area, no mouse buttons are clicked' event handler             |
            //+-------------------------------------------------------------------------------------------------+
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_NOT_PRESSED)
              {
               form.OnMouseEvent(MOUSE_EVENT_INSIDE_CONTROL_AREA_NOT_PRESSED,lparam,dparam,sparam);
              }
            //+---------------------------------------------------------------------------------------------+
            //| 'The cursor is inside the control area, a mouse button is clicked (any)' event handler      |
            //+---------------------------------------------------------------------------------------------+
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED)
              {
               form.OnMouseEvent(MOUSE_EVENT_INSIDE_CONTROL_AREA_PRESSED,lparam,dparam,sparam);
              }
            //+---------------------------------------------------------------------------------------------+
            //| 'The cursor is inside the control area, the mouse wheel is being scrolled' event handler    |
            //+---------------------------------------------------------------------------------------------+
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL)
              {
               form.OnMouseEvent(MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL,lparam,dparam,sparam);
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

É a partir daqui que os manipuladores de eventos de objeto são chamados quando o cursor está dentro da área de controle.

Agora estamos prontos para o teste. Vamos verificar o fizemos até agora.


Teste

Para testar, vamos pegar o Expert Advisor do artigo anterior e salvá-lo na nova pasta \MQL5\Experts\TestDoEasy\Part123\ com o novo nome TestDoEasy123.mq5.

Especificamos o número de painéis criados, usando uma macro substituição. Vamos colocar o valor 1, indicando que vamos criar apenas um painel por enquanto:

//+------------------------------------------------------------------+
//|                                                     TstDE123.mq5 |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- defines
#define  FORMS_TOTAL (1)   // Number of created forms
#define  START_X     (4)   // Initial X coordinate of the shape
#define  START_Y     (4)   // Initial Y coordinate of the shape
#define  KEY_LEFT    (65)  // (A) Left
#define  KEY_RIGHT   (68)  // (D) Right
#define  KEY_UP      (87)  // (W) Up
#define  KEY_DOWN    (88)  // (X) Down
#define  KEY_FILL    (83)  // (S) Filling
#define  KEY_ORIGIN  (90)  // (Z) Default
#define  KEY_INDEX   (81)  // (Q) By index


No manipulador OnInit(), adicionamos esta macro substituição ao ciclo de criação do painel. Vamos aumentar a espessura do separador do controle SplitContainer em dois pixels, para que o retângulo pontilhado fique mais evidente e atraente visualmente.
Ao obter ponteiros para objetos formas criados, usaremos o método de obtenção por descrição de objeto.

Isso garante que seja especificada a descrição do objeto quando ele é criado, facilitando a identificação e manipulação do mesmo
:

//+------------------------------------------------------------------+
//| 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<FORMS_TOTAL;i++)
     {
      pnl=engine.CreateWFPanel("WinForms Panel"+(string)i,(i==0 ? 50 : 70),(i==0 ? 50 : 70),410,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false);
      if(pnl!=NULL)
        {
         pnl.Hide();
         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(6+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<FORMS_TOTAL;i++)
     {
      pnl=engine.GetWFPanel("WinForms Panel"+(string)i);
      if(pnl!=NULL)
        {
         pnl.Show();
         pnl.Redraw(true);
        }
     }
        
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Compilamos o Expert Advisor e o iniciamos no gráfico:


Bem, quando o título da guia selecionada ultrapassa a borda do contêiner e o painel é movido, não há mais defeitos visuais na forma de excedente de parte do título. A lacuna entre os títulos e os controles de rolagem agora é menor e mais organizada quando a barra de título está na vertical. Isso melhora a aparência geral e a navegação do usuário.

Quando os títulos estão localizados à direita, o lado direito dos painéis de controle é levemente cortado (embora isso seja invisível), o que impede o cursor de interagir com as partes ocultas dos painéis. Corrigimos isso para que possamos trabalhar calmamente e corretamente com os títulos das abas. O mesmo ocorre quando os painéis são reduzidos por um separador, o que pode esconder a inscrição no painel criada por um objeto da classe CLabel. O cursor está localizado fisicamente sobre as marcas gráficas, mas elas são cortadas e o cursor está praticamente sobre sua área invisível e o objeto não é processado.
Além disso, o separador do controle SplitContainer agora fica mais bonito ao interagir com o mouse.


O que virá a seguir?

No próximo artigo, continuaremos o desenvolvimento de controles de biblioteca.

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.

Voltar ao conteúdo

*Artigos desta série:

 
DoEasy. Controles (Parte 20): Objeto WinForms SplitContainer
DoEasy. Controles (Parte 21): O controle SplitContainer. Separador de painéis
DoEasy. Controles (Parte 22): SplitContainer. Alterando as propriedades do objeto criado



Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/11634

Arquivos anexados |
MQL5.zip (4482.83 KB)
Indicadores adaptativos Indicadores adaptativos
Neste artigo, exploraremos diferentes enfoques para desenvolver indicadores adaptativos. Esses indicadores se destacam pelo uso de feedback entre as entradas e saídas, o que permite que eles se adaptem de forma autônoma para processar séries temporais financeiras de forma eficiente.
Funcionalidades do assistente MQL5 que você precisa conhecer (Parte 03): Entropia de Shannon Funcionalidades do assistente MQL5 que você precisa conhecer (Parte 03): Entropia de Shannon
O trader de hoje é um filomata que está quase sempre procurando novas ideias, experimentando-as, escolhendo modificá-las ou descartá-las; um processo exploratório que deve custar uma quantidade razoável de diligência. Esta série de artigos proporá que o assistente MQL5 deve ser um esteio para os traders.
Explorando a magia dos períodos de negociação com o auxílio do Frames Analyzer Explorando a magia dos períodos de negociação com o auxílio do Frames Analyzer
Bem, o Frames Analyzer é uma ferramenta para analisar quadros de otimização durante o processo de otimização de parâmetros quer seja no testador de estratégia ou fora do mesmo. Ele permite ler arquivos MQD ou bancos de dados criados após a otimização de parâmetros e compartilhar esses resultados com outros usuários da ferramenta. Ele é projetado para auxiliar a melhorar estratégias de negociação conjuntamente. Adicionalmente, é bom mencionar que quadro de otimização é um conjunto de dados que contém informações sobre as condições de mercado em um determinado momento, como preços, volumes, indicadores técnicos, entre outros, que são usados para avaliar e comparar a eficácia de diferentes estratégias de negociação.
Redes neurais de maneira fácil (Parte 31): Algoritmos evolutivos Redes neurais de maneira fácil (Parte 31): Algoritmos evolutivos
No último artigo, iniciamos a análise dos métodos de otimização sem gradiente, e nos familiarizamos com o algoritmo genético. Hoje, continuaremos a discutir o mesmo assunto e também examinaremos outra classe de algoritmos evolutivos.