Gráficos na biblioteca DoEasy (Parte 73): objeto-forma de um elemento gráfico

Artyom Trishkin | 26 julho, 2021

Sumário


Ideia

Os programas modernos, especialmente os programas analíticos, podem usar grandes quantidades de dados que sem uma correta visualização se tornariam impossíveis de entender. Além disso, é muito difícil usar um programa em toda a sua extensão se ele não fornecer ao usuário uma interface clara e conveniente que facilite uma experiência interativa. Naturalmente, a capacidade de trabalhar com gráficos não pode deixar de estar nesta biblioteca. Por isso, hoje daremos início a uma seção grande sobre como trabalhar com elementos gráficos.

Nosso objetivo: criar uma funcionalidade conveniente para gerar uma ampla gama de objetos gráficos, ensinar todas as classes principais da biblioteca a trabalhar interativamente com gráficos (com seus objetos gráficos), bem como criar objetos gráficos com uma hierarquia que possua componentes de qualquer complexidade.

Vamos começar com objetos gráficos baseados na classe da biblioteca padrão CCanvas. Esta classe permite criar facilmente desenhos personalizados e usá-los como "tijolos" para construir objetos mais complexos. Podemos optar por usar imagens pré-preparadas, só que desenhá-las independentemente numa tela criada por nós é mais interessante. Vamos implementar isto último amplamente para desenhar nossos objetos gráficos.

A hierarquia de cada objeto sempre ficará assim:

  1. Objeto base de todos os elementos gráficos da biblioteca com base na classe CObject. Neste objeto será declarado o objeto da classe CCanvas e conterá todos os parâmetros gerais dos elementos gráficos, como largura, altura, coordenadas no gráfico, bordas direita e inferior do objeto, etc.,
  2. O objeto-forma do elemento gráfico representará a base (tela) de qualquer objeto gráfico - nele colocaremos todos os outros elementos do objeto composto, e usando seus parâmetros poderemos definir parâmetros para todo o gráfico objeto. Aqui, será declarado um objeto da classe que fornece métodos para trabalhar com o estado do mouse - as coordenadas do cursor e os botões pressionados.

Isto é o mínimo necessário para formar o elemento base que compreende todos os objetos gráficos da biblioteca criados com base na classe CCanvas. Todos os outros objetos serão criados com base neste objeto e herdarão suas propriedades básicas.

Mas, primeiro, modificaremos ligeiramente as classes prontas da biblioteca e adicionaremos novos dados para os objetos criados hoje.


Aprimorando as classes da biblioteca

Ao arquivo \MQL5\Include\DoEasy\Defines.mqh adicionamos uma nova subseção de parâmetros da tela e inserimos a substituição de macro que contém sua frequência de atualização:

//--- Parameters of the DOM snapshot series
#define MBOOKSERIES_DEFAULT_DAYS_COUNT (1)                        // The default required number of days for DOM snapshots in the series
#define MBOOKSERIES_MAX_DATA_TOTAL     (200000)                   // Maximum number of stored DOM snapshots of a single symbol
//--- Canvas parameters
#define PAUSE_FOR_CANV_UPDATE          (16)                       // Canvas update frequency
//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+

Na verdade, precisamos atualizar (redesenhar) objetos com base na tela não mais do que a cada 16 milissegundos, para contornar o redesenho desnecessário da tela, invisível ao olho humano, mas que dá uma carga desnecessária no sistema. Por essa razão, antes de atualizar o objeto baseado na tela, primeiro verificaremos quantos milissegundos se passaram desde sua atualização anterior. Ao definir a latência ideal, podemos conseguir uma exibição de tela com objetos gráficos aceitável.

Para definir o estado dos botões do mouse e as teclas Shift e Ctrl, criaremos uma classe de objeto-estado do mouse. Para isso, precisamos de duas enumerações: lista de possíveis estados dos botões do mouse e das teclas Shift e Ctrl e lista de possíveis estados do mouse em relação à forma. Vamos adicioná-las ao final da lista de arquivos:

//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Data for working with mouse                                      |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| The list of possible mouse buttons, Shift and Ctrl keys states   |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_BUTT_KEY_STATE
  {
   MOUSE_BUTT_KEY_STATE_NONE              = 0,        // Nothing is clicked
//--- Mouse buttons
   MOUSE_BUTT_KEY_STATE_LEFT              = 1,        // The left mouse button is clicked
   MOUSE_BUTT_KEY_STATE_RIGHT             = 2,        // The right mouse button is clicked
   MOUSE_BUTT_KEY_STATE_MIDDLE            = 16,       // The middle mouse button is clicked
   MOUSE_BUTT_KEY_STATE_WHELL             = 128,      // Scrolling the mouse wheel
   MOUSE_BUTT_KEY_STATE_X1                = 32,       // The first additional mouse button is clicked
   MOUSE_BUTT_KEY_STATE_X2                = 64,       // The second additional mouse button is clicked
   MOUSE_BUTT_KEY_STATE_LEFT_RIGHT        = 3,        // The left and right mouse buttons clicked
//--- Keyboard keys
   MOUSE_BUTT_KEY_STATE_SHIFT             = 4,        // Shift is being held
   MOUSE_BUTT_KEY_STATE_CTRL              = 8,        // Ctrl is being held
   MOUSE_BUTT_KEY_STATE_CTRL_CHIFT        = 12,       // Ctrl and Shift are being held
//--- Left mouse button combinations
   MOUSE_BUTT_KEY_STATE_LEFT_WHELL        = 129,      // The left mouse button is clicked and the wheel is being scrolled
   MOUSE_BUTT_KEY_STATE_LEFT_SHIFT        = 5,        // The left mouse button is clicked and Shift is being held
   MOUSE_BUTT_KEY_STATE_LEFT_CTRL         = 9,        // The left mouse button is clicked and Ctrl is being held
   MOUSE_BUTT_KEY_STATE_LEFT_CTRL_CHIFT   = 13,       // The left mouse button is clicked, Ctrl and Shift are being held
//--- Right mouse button combinations
   MOUSE_BUTT_KEY_STATE_RIGHT_WHELL       = 130,      // The right mouse button is clicked and the wheel is being scrolled
   MOUSE_BUTT_KEY_STATE_RIGHT_SHIFT       = 6,        // The right mouse button is clicked and Shift is being held
   MOUSE_BUTT_KEY_STATE_RIGHT_CTRL        = 10,       // The right mouse button is clicked and Ctrl is being held
   MOUSE_BUTT_KEY_STATE_RIGHT_CTRL_CHIFT  = 14,       // The right mouse button is clicked, Ctrl and Shift are being held
//--- Middle mouse button combinations
   MOUSE_BUTT_KEY_STATE_MIDDLE_WHEEL      = 144,      // The middle mouse button is clicked and the wheel is being scrolled
   MOUSE_BUTT_KEY_STATE_MIDDLE_SHIFT      = 20,       // The middle mouse button is clicked and Shift is being held
   MOUSE_BUTT_KEY_STATE_MIDDLE_CTRL       = 24,       // The middle mouse button is clicked and Ctrl is being held
   MOUSE_BUTT_KEY_STATE_MIDDLE_CTRL_CHIFT = 28,       // The middle mouse button is clicked, Ctrl and Shift are being held
  };
//+------------------------------------------------------------------+
//| 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_NOT_PRESSED,              // The cursor is outside the form, the mouse buttons are not clicked
   MOUSE_FORM_STATE_OUTSIDE_PRESSED,                  // The cursor is outside the form, any mouse button is clicked
   MOUSE_FORM_STATE_OUTSIDE_WHEEL,                    // The cursor is outside the form, the mouse wheel is being scrolled
//--- Within the form
   MOUSE_FORM_STATE_INSIDE_NOT_PRESSED,               // The cursor is inside the form, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_PRESSED,                   // The cursor is inside the form, any mouse button is clicked
   MOUSE_FORM_STATE_INSIDE_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
//--- Within the window scrolling area
   MOUSE_FORM_STATE_INSIDE_SCROLL_NOT_PRESSED,        // The cursor is within the window scrolling area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_SCROLL_PRESSED,            // The cursor is within the window scrolling area, any mouse button is clicked
   MOUSE_FORM_STATE_INSIDE_SCROLL_WHEEL,              // The cursor is within the window scrolling area, the mouse wheel is being scrolled
  };
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Data for handling graphical elements                             |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| The list of graphical element types                              |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_FORM,                           // Simple form
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Window
  };
//+------------------------------------------------------------------+

Adicionamos a lista de tipos de elementos gráficos como um "assento reservado" para as outras classes com base nas criadas hoje - essas listas serão complementada e usadas em artigos futuros.

Na lista de estados possíveis dos botões do mouse e das teclas Shift e Ctrl temos eventos básicos de mouse e tecla, bem como algumas de suas combinações que provavelmente serão necessárias com mais frequência.
Basicamente, os estados do mouse são um conjunto simples de sinalizadores de bits descritos na ajuda para o evento CHARTEVENT_MOUSE_MOVE.

A tabela contém os bits e os estados correspondentes dos botões do mouse e das teclas Shift e Ctrl:

Bit
 Descrição Valor
0
 Estado do botão esquerdo do mouse
 1
1
 Estado do botão direito do mouse
 2
2
 Estado da tecla SHIFT
 4
3
 Estado da tecla CTRL
 8
4
 Estado do botão do meio do mouse
 16
5
 Estado do primeiro botão adicional do mouse
 32
6
 Estado do segundo botão adicional do mouse
 64

Esta tabela mostra como os valores dos eventos do mouse podem ser determinados pelo número escrito na variável que armazena os bits dos estados do mouse:

É por esta razão que os valores na enumeração ENUM_MOUSE_BUTT_KEY_STATE estão definidos exatamente de acordo com o cálculo mostrado dos valores da variável com os sinalizadores definidos, descritos pelas constantes desta enumeração.

Enumeração ENUM_MOUSE_FORM_STATE serve para indicar a posição do cursor do mouse em relação à forma com os botões do mouse pressionados/não-pressionados. Precisaremos dos valores das constantes desta enumeração para determinar a posição relativa do cursor do mouse, seus botões e o objeto com o qual devemos interagir.

Podemos armazenar essas duas enumerações em dois bytes da variável ushort e por seu valor, assim entenderemos o que está acontecendo com o mouse e seu objeto de interação. A tabela mostra todo o bitmap desta variável:

 Bit  Byte Status
Valor
0
0
 botão esquerdo do mouse
1
1
0
 botão direito do mouse
2
2
0
 tecla Shift
4
3
0
 tecla CTRL
8
4
0
 botão do meio do mouse
16
5
0
 primeiro botão adicional do mouse
32
6
0
 segundo botão adicional do mouse
64
7
0
 rodinha do mouse
128
8 (0)
1
 cursor dentro da forma
256
9 (1)
1
 cursor dentro da área ativa da forma
512
10 (2)
1
 cursor na área de controle da janela (minimizar/maximizar/fechar, etc.)
1024
11 (3)
1
 cursor na área de rolagem da janela
2048
12 (4)
1
 cursor no lado esquerdo da forma
4096
13 (5)
1
 cursor na borda inferior da forma
8192
14 (6)
1
 cursor na borda direita da forma
16384
15 (7)
1
 cursor no topo da forma
32768

Por enquanto, esses sinalizadores de estados do mouse e de localização do cursor em relação tanto ao objeto-forma quanto ao objeto-janela baseado na forma serão suficientes para nós.

Vamos modificar ligeiramente o objeto da classe de pausa no arquivo \MQL5\Include\DoEasy\Services\Pause.mqh.
Seu método SetTimeBegin(), além de definir uma nova contagem regressiva da pausa, também registra o tempo passado para o método na variável m_time_begin.
Isso só é necessário para enviar informações para o log e não é necessário se quisermos apenas contar uma pausa dentro do método. É fácil transferir qualquer tempo para o método (inclusive zero), mas, mesmo assim, decidi fazer a sobrecarga do método - sem especificar o tempo:

//--- Set the new (1) countdown start time and (2) pause in milliseconds
   void              SetTimeBegin(const ulong time)         { this.m_time_begin=time; this.SetTimeBegin();              }
   void              SetTimeBegin(void)                     { this.m_start=this.TickCount();                            }
   void              SetWaitingMSC(const ulong pause)       { this.m_wait_msc=pause;                                    }

Agora podemos criar uma classe de objeto-estados do mouse.


Classe de estados do mouse

Na pasta de funções e classes de serviço \MQL5\Include\DoEasy\Services\ criamos uma nova classe CMouseState no arquivo MouseState.mqh.

Na seção privada da classe declaramos as variáveis para armazenar parâmetros de objeto, dois métodos para definir sinalizadores de estados de botões e teclas do mouse, e vamos deixar um "lembrete" da localização dos bits sinalizadores na variável ushort para armazenar os sinalizadores de bits do estado do mouse:

//+------------------------------------------------------------------+
//|                                                   MouseState.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "DELib.mqh"
//+------------------------------------------------------------------+
//| Mouse status class                                               |
//+------------------------------------------------------------------+
class CMouseState
  {
private:
   int               m_coord_x;                             // X coordinate
   int               m_coord_y;                             // Y coordinate
   int               m_delta_wheel;                         // Mouse wheel scroll value
   int               m_window_num;                          // Subwindow index
   long              m_chart_id;                            // Chart ID
   ushort            m_state_flags;                         // Status flags
   
//--- Set the status of mouse buttons, as well as of Shift and Ctrl keys
   void              SetButtonKeyState(const int id,const long lparam,const double dparam,const ushort flags);
//--- Set the mouse buttons and keys status flags
   void              SetButtKeyFlags(const short flags);

//--- Data location in the ushort value of the button status
   //-----------------------------------------------------------------
   //   bit    |    byte   |            state            |    dec    |
   //-----------------------------------------------------------------
   //    0     |     0     | left mouse button           |     1     |
   //-----------------------------------------------------------------
   //    1     |     0     | right mouse button          |     2     |
   //-----------------------------------------------------------------
   //    2     |     0     | SHIFT button                |     4     |
   //-----------------------------------------------------------------
   //    3     |     0     | CTRL button                 |     8     |
   //-----------------------------------------------------------------
   //    4     |     0     | middle mouse button         |    16     |
   //-----------------------------------------------------------------
   //    5     |     0     | 1 add. mouse button         |    32     |
   //-----------------------------------------------------------------
   //    6     |     0     | 2 add. mouse button         |    64     |
   //-----------------------------------------------------------------
   //    7     |     0     | scrolling the wheel         |    128    |
   //-----------------------------------------------------------------
   //-----------------------------------------------------------------
   //    0     |     1     | cursor inside the form      |    256    |
   //-----------------------------------------------------------------
   //    1     |     1     | cursor inside active area   |    512    |
   //-----------------------------------------------------------------
   //    2     |     1     | cursor in the control area  |   1024    |
   //-----------------------------------------------------------------
   //    3     |     1     | cursor in the scrolling area|   2048    |
   //-----------------------------------------------------------------
   //    4     |     1     | cursor at the left edge     |   4096    |
   //-----------------------------------------------------------------
   //    5     |     1     | cursor at the bottom edge   |   8192    |
   //-----------------------------------------------------------------
   //    6     |     1     | cursor at the right edge    |   16384   |
   //-----------------------------------------------------------------
   //    7     |     1     | cursor at the top edge      |   32768   |
   //-----------------------------------------------------------------
      
public:

Na seção pública da classe, escrevemos os métodos que retornam os valores das variáveis - propriedades do objeto:

public:
//--- Reset the states of all buttons and keys
   void              ResetAll(void);
//--- Set (1) the subwindow index and (2) the chart ID
   void              SetWindowNum(const int wnd_num)           { this.m_window_num=wnd_num;        }
   void              SetChartID(const long id)                 { this.m_chart_id=id;               }
//--- Return the variable with the mouse status flags
   ushort            GetMouseFlags(void)                       { return this.m_state_flags;        }
//--- Return (1-2) the cursor coordinates, (3) scroll wheel value, (4) status of the mouse buttons and Shift/Ctrl keys
   int               CoordX(void)                        const { return this.m_coord_x;            }
   int               CoordY(void)                        const { return this.m_coord_y;            }
   int               DeltaWheel(void)                    const { return this.m_delta_wheel;        }
   ENUM_MOUSE_BUTT_KEY_STATE ButtKeyState(const int id,const long lparam,const double dparam,const string flags);

//--- Return the flag of the clicked (1) left, (2) right, (3) middle, (4) first and (5) second additional mouse buttons
   bool              IsPressedButtonLeft(void)           const { return this.m_state_flags==1;     }
   bool              IsPressedButtonRight(void)          const { return this.m_state_flags==2;     }
   bool              IsPressedButtonMiddle(void)         const { return this.m_state_flags==16;    }
   bool              IsPressedButtonX1(void)             const { return this.m_state_flags==32;    }
   bool              IsPressedButtonX2(void)             const { return this.m_state_flags==64;    }
//--- Return the flag of the pressed (1) Shift, (2) Ctrl, (3) Shift+Ctrl key and the flag of scrolling the mouse wheel
   bool              IsPressedKeyShift(void)             const { return this.m_state_flags==4;     }
   bool              IsPressedKeyCtrl(void)              const { return this.m_state_flags==8;     }
   bool              IsPressedKeyCtrlShift(void)         const { return this.m_state_flags==12;    }
   bool              IsWheel(void)                       const { return this.m_state_flags==128;   }

//--- Return the flag indicating the status of the left mouse button and (1) the mouse wheel, (2) Shift, (3) Ctrl, (4) Ctrl+Shift
   bool              IsPressedButtonLeftWheel(void)      const { return this.m_state_flags==129;   }
   bool              IsPressedButtonLeftShift(void)      const { return this.m_state_flags==5;     }
   bool              IsPressedButtonLeftCtrl(void)       const { return this.m_state_flags==9;     }
   bool              IsPressedButtonLeftCtrlShift(void)  const { return this.m_state_flags==13;    }
//--- Return the flag indicating the status of the right mouse button and (1) the mouse wheel, (2) Shift, (3) Ctrl, (4) Ctrl+Shift
   bool              IsPressedButtonRightWheel(void)     const { return this.m_state_flags==130;   }
   bool              IsPressedButtonRightShift(void)     const { return this.m_state_flags==6;     }
   bool              IsPressedButtonRightCtrl(void)      const { return this.m_state_flags==10;    }
   bool              IsPressedButtonRightCtrlShift(void) const { return this.m_state_flags==14;    }
//--- Return the flag indicating the status of the middle mouse button and (1) the mouse wheel, (2) Shift, (3) Ctrl, (4) Ctrl+Shift
   bool              IsPressedButtonMiddleWheel(void)    const { return this.m_state_flags==144;   }
   bool              IsPressedButtonMiddleShift(void)    const { return this.m_state_flags==20;    }
   bool              IsPressedButtonMiddleCtrl(void)     const { return this.m_state_flags==24;    }
   bool              IsPressedButtonMiddleCtrlShift(void)const { return this.m_state_flags==28;    }

//--- Constructor/destructor
                     CMouseState();
                    ~CMouseState();
  };
//+------------------------------------------------------------------+

Aqui implementamos métodos que retornam os valores das variáveis da classe, bem como alguns métodos que retornam estados predefinidos dos botões do mouse e as teclas Ctrl e Shift.


No construtor da classe é chamado o método que redefine o estado dos sinalizadores de botões e de teclas e que zera a quantidade de giro da roda do mouse:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CMouseState::CMouseState() : m_delta_wheel(0),m_coord_x(0),m_coord_y(0),m_window_num(0)
  {
   this.ResetAll();
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CMouseState::~CMouseState()
  {
  }
//+------------------------------------------------------------------+
//| Reset the states of all buttons and keys                         |
//+------------------------------------------------------------------+
void CMouseState::ResetAll(void)
  {
   this.m_delta_wheel = 0;
   this.m_state_flags = 0;
  }
//+------------------------------------------------------------------+

Método que define o estado dos botões do mouse e das teclas Shift e Ctrl:

//+------------------------------------------------------------------+
//| Set the status of mouse buttons, as well as of Shift/Ctrl keys   |
//+------------------------------------------------------------------+
void CMouseState::SetButtonKeyState(const int id,const long lparam,const double dparam,const ushort flags)
  {
   //--- Reset the values of all mouse status bits
   this.ResetAll();
   //--- If a chart or an object is left-clicked
   if(id==CHARTEVENT_CLICK || id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Write the appropriate chart coordinates and set the bit of 0
      this.m_coord_x=(int)lparam;
      this.m_coord_y=(int)dparam;
      this.m_state_flags |=(0x0001);
     }
   //--- otherwise
   else
     {
      //--- in case of a mouse wheel scrolling
      if(id==CHARTEVENT_MOUSE_WHEEL)
        {
         //--- get the cursor coordinates and the total scroll value (the minimum of +120 or -120)
         this.m_coord_x=(int)(short)lparam;
         this.m_coord_y=(int)(short)(lparam>>16);
         this.m_delta_wheel=(int)dparam;
         //--- Call the method of setting flags indicating the states of the mouse buttons and Shift/Ctrl keys
         this.SetButtKeyFlags((short)(lparam>>32));
         //--- and set the bit of 8
         this.m_state_flags &=0xFF7F;
         this.m_state_flags |=(0x0001<<7);
        }
      //--- If this is a cursor movement, write its coordinates and
      //--- call the method of setting flags indicating the states of the mouse buttons and Shift/Ctrl keys
      if(id==CHARTEVENT_MOUSE_MOVE)
        {
         this.m_coord_x=(int)lparam;
         this.m_coord_y=(int)dparam;
         this.SetButtKeyFlags(flags);
        }
     }
  }
//+------------------------------------------------------------------+

Aqui verificamos qual evento de gráfico estamos processando.
Primeiro, zeramos todos os bits na variável que armazena os sinalizadores de bits do estado do mouse.
Em seguida, para o clique num gráfico ou objeto definimos o bit 0 da variável que armazena os sinalizadores de bit.
Para o evento de rolagem da roda do mouse, o parâmetro inteiro lparam contém dados sobre as coordenadas do cursor, a quantidade de rolagem e sinalizadores de bits do estado dos botões e teclas Ctrl e Shift. Extraímos todos os dados da variável lparam e os escrevemos nas variáveis que armazenam as coordenadas do cursor e em nossa própria variável com sinalizadores de bits de forma que seja mantida a ordem dos bits descrita na seção privada da classe. Em seguida, definimos o bit 8 que sinaliza a rolagem da roda do mouse.
Para o movimento do cursor sobre o gráfico escrevemos as coordenadas do cursor em variáveis e chamamos o método para definir sinalizadores de bits sobre o estado dos botões do mouse e das teclas Ctrl e Shift.

Método que define os sinalizadores dos estados dos botões e teclas do mouse:

//+------------------------------------------------------------------+
//| Set the mouse buttons and keys status flags                      |
//+------------------------------------------------------------------+
void CMouseState::SetButtKeyFlags(const short flags)
  {
//--- Left mouse button status
   if((flags & 0x0001)!=0) this.m_state_flags |=(0x0001<<0);
//--- Right mouse button status
   if((flags & 0x0002)!=0) this.m_state_flags |=(0x0001<<1);
//--- SHIFT status
   if((flags & 0x0004)!=0) this.m_state_flags |=(0x0001<<2);
//--- CTRL status
   if((flags & 0x0008)!=0) this.m_state_flags |=(0x0001<<3);
//--- Middle mouse button status
   if((flags & 0x0010)!=0) this.m_state_flags |=(0x0001<<4);
//--- The first additional mouse button status
   if((flags & 0x0020)!=0) this.m_state_flags |=(0x0001<<5);
//--- The second additional mouse button status
   if((flags & 0x0040)!=0) this.m_state_flags |=(0x0001<<6);
  }
//+------------------------------------------------------------------+

Tudo é simples aqui: ao método é transferida a variável com os sinalizadores do estado do mouse. Nele colocamos uma máscara de bits de cada com o conjunto de bits verificado. O valor obtido após a aplicação da máscara de bits usando o bit a bit "E" será 'true' somente se os dois bits verificados estiverem definidos (1). Se a variável com a máscara não for igual a zero (o bit verificado está definido), escreveremos o bit correspondente na variável para armazenar sinalizadores de bit.

Método que retorna o estado dos botões do mouse e das teclas Shift e Ctrl:

//+------------------------------------------------------------------+
//| Return the mouse buttons and Shift/Ctrl keys states              |
//+------------------------------------------------------------------+
ENUM_MOUSE_BUTT_KEY_STATE CMouseState::ButtKeyState(const int id,const long lparam,const double dparam,const string flags)
  {
   this.SetButtonKeyState(id,lparam,dparam,(ushort)flags);
   return (ENUM_MOUSE_BUTT_KEY_STATE)this.m_state_flags;
  }
//+------------------------------------------------------------------+

Aqui, primeiro chamamos o método que verifica e define todas as sinalizações do estado do mouse e as teclas Ctrl e Shift, e retornamos o valor da variável m_state_flags como enumerações ENUM_MOUSE_BUTT_KEY_STATE. Nessa enumeração, os valores de todas as constantes correspondem ao valor obtido pelo conjunto de bits definidos pela variável. Assim, retornamos imediatamente um dos valores de enumeração que processaremos posteriormente nas classes onde é necessário obter o estado do mouse, seus botões e as teclas Ctrl e Shift. Este método é chamado a partir do manipulador OnChartEvent().


Classe base de objeto que compreende todos os elementos gráficos da biblioteca

Da mesma forma que as classes principais da biblioteca, herdamos a classe base da biblioteca padrão, todas as classes de objetos de elementos gráficos devem ser herdadas dela. Esse tipo de herança nos permitirá trabalhar com cada objeto gráfico como um objeto padrão MQL5, ou seja, é importante para nós podermos trabalhar com diferentes tipos de objetos gráficos, bem como com o objeto da classe CObject. Para fazer isso, precisamos criar um novo objeto base que herdará o objeto CObject e que conterá variáveis e métodos comuns para cada (e qualquer) objeto gráfico da biblioteca.

Como propriedades gerais inerentes a cada objeto gráfico e incluídas no objeto gráfico base, teremos:

A classe será muito simples: variáveis privadas, métodos protegidos para instalação e métodos públicos para retorno de seus valores.
A classe será herdeira da classe base da biblioteca padrão CObject.

No diretório da biblioteca \MQL5\Include\DoEasy\Objects\ criamos uma nova pasta Graph\, e nela o novo arquivo GBaseObj.mqh da classe CGBaseObj:

//+------------------------------------------------------------------+
//|                                                     GBaseObj.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\..\Services\DELib.mqh"
//+------------------------------------------------------------------+
//| Class of the base object of the library graphical objects        |
//+------------------------------------------------------------------+
class CGBaseObj : public CObject
  {
private:
   int               m_type;                                // Object type
   string            m_name_obj;                            // Object name
   long              m_chart_id;                            // Chart ID
   int               m_wnd_num;                             // Chart subwindow index
   int               m_coord_x;                             // Canvas X coordinate
   int               m_coord_y;                             // Canvas Y coordinate
   int               m_width;                               // Width
   int               m_height;                              // Height
   bool              m_movable;                             // Object movability flag
   bool              m_selectable;                          // Object selectability flag

protected:
//--- Set the values to class variables
   void              SetNameObj(const string name)             { this.m_name_obj=name;                            }
   void              SetChartID(const long chart_id)           { this.m_chart_id=chart_id;                        }
   void              SetWindowNum(const int wnd_num)           { this.m_wnd_num=wnd_num;                          }
   void              SetCoordX(const int coord_x)              { this.m_coord_x=coord_x;                          }
   void              SetCoordY(const int coord_y)              { this.m_coord_y=coord_y;                          }
   void              SetWidth(const int width)                 { this.m_width=width;                              }
   void              SetHeight(const int height)               { this.m_height=height;                            }
   void              SetMovable(const bool flag)               { this.m_movable=flag;                             }
   void              SetSelectable(const bool flag)            { this.m_selectable=flag;                          }
   
public:
//--- Return the values of class variables
   string            NameObj(void)                       const { return this.m_name_obj;                          }
   long              ChartID(void)                       const { return this.m_chart_id;                          }
   int               WindowNum(void)                     const { return this.m_wnd_num;                           }
   int               CoordX(void)                        const { return this.m_coord_x;                           }
   int               CoordY(void)                        const { return this.m_coord_y;                           }
   int               Width(void)                         const { return this.m_width;                             }
   int               Height(void)                        const { return this.m_height;                            }
   int               RightEdge(void)                     const { return this.m_coord_x+this.m_width;              }
   int               BottomEdge(void)                    const { return this.m_coord_y+this.m_height;             }
   bool              Movable(void)                       const { return this.m_movable;                           }
   bool              Selectable(void)                    const { return this.m_selectable;                        }
   
//--- The virtual method returning the object type
   virtual int       Type(void)                          const { return this.m_type;                              }

//--- Constructor/destructor
                     CGBaseObj();
                    ~CGBaseObj();
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CGBaseObj::CGBaseObj() : m_chart_id(::ChartID()),
                         m_type(WRONG_VALUE),
                         m_wnd_num(0),
                         m_coord_x(0),
                         m_coord_y(0),
                         m_width(0),
                         m_height(0),
                         m_movable(false),
                         m_selectable(false)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CGBaseObj::~CGBaseObj()
  {
  }
//+------------------------------------------------------------------+

A classe de objeto base CObject implementa o método virtual Type() que retorna o tipo do objeto (para identificar objetos por tipo). O método original sempre retorna zero:

   //--- method of identifying the object
   virtual int       Type(void)                                    const { return(0);      }

Ao substituir este método nos herdeiros, retornamos de cada objeto o tipo definido na variável m_type.
Os tipos de objetos gráficos serão definidos em outros artigos - ao criar classes desses objetos. Por enquanto, o método retornará -1 (nós definimos este valor na lista de inicialização do construtor de classe).


Classe de objeto-forma de elementos gráficos

E agora fazemos a criação da classe do objeto-forma. O objeto-forma será a base para a criação das demais classes de elementos gráficos da biblioteca baseada na classe CCanvas. Será como uma "tela" na qual desenharemos os dados necessários para diferentes objetos e colocaremos os elementos restantes cujo conjunto acabará por exibir o objeto acabado.

Mas, por hoje, será apenas uma forma simples com parâmetros básicos e funcionalidades básicas (a capacidade de definir uma área ativa que serve para interagir com o cursor), bem como a capacidade de movê-la pelo gráfico.

Na pasta da biblioteca \MQL5\Include\DoEasy\Objects\Graph\ criamos um novo arquivo Form.mqh da classe CForm.
A classe deve ser herdada do objeto base de todos os objetos gráficos da biblioteca. Assim, ele deve incluir arquivos da classe do objeto gráfico base e da classe do objeto-propriedades do mouse:

//+------------------------------------------------------------------+
//|                                                         Form.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Canvas\Canvas.mqh>
#include "GBaseObj.mqh"
#include "..\..\Services\MouseState.mqh"
//+------------------------------------------------------------------+
//| Class of the base object of the library graphical objects        |
//+------------------------------------------------------------------+
class CForm : public CGBaseObj
  {
  }

Na seção protegida da classe declaramos um objeto da classe da biblioteca padrão CCanvas, os objetos de classes da biblioteca CPause e CMouseState, uma variável para armazenar o valor dos estados do mouse, uma variável para armazenar os sinalizadores de bits do estado do mouse e as variáveis para armazenar as propriedades do objeto:

//+------------------------------------------------------------------+
//| Class of the base object of the library graphical objects        |
//+------------------------------------------------------------------+
class CForm : public CGBaseObj
  {
protected:
   CCanvas              m_canvas;                              // CCanvas class object
   CPause               m_pause;                               // Pause class object
   CMouseState          m_mouse;                               // "Mouse status" class object
   ENUM_MOUSE_FORM_STATE m_mouse_state;                        // Mouse status relative to the form
   ushort               m_mouse_state_flags;                   // Mouse status flags
   
   int                  m_act_area_left;                       // Left border of the active area (offset from the left border inward)
   int                  m_act_area_right;                      // Right border of the active area (offset from the right border inward)
   int                  m_act_area_top;                        // Upper border of the active area (offset from the upper border inward)
   int                  m_act_area_bottom;                     // Lower border of the active area (offset from the lower border inward)
   uchar                m_opacity;                             // Opacity
   int                  m_shift_y;                             // Y coordinate shift for the subwindow
   
private:

Na seção privada da classe, declararemos os métodos auxiliares para a operação da classe:

private:
//--- Set and return the flags indicating the states of mouse buttons and Shift/Ctrl keys
   ENUM_MOUSE_BUTT_KEY_STATE MouseButtonKeyState(const int id,const long lparam,const double dparam,const string sparam)
                       {
                        return this.m_mouse.ButtKeyState(id,lparam,dparam,sparam);
                       }
//--- Return the cursor position relative to the (1) form and (2) active area
   bool              CursorInsideForm(const int x,const int y);
   bool              CursorInsideActiveArea(const int x,const int y);

public:

O método MouseButtonKeyState() retorna o valor retornado pelo método de mesmo nome desde o objeto da classe de estados do mouse, os outros dois métodos são necessários para determinar a localização do cursor do mouse em relação à forma e à área ativa deste. Iremos considerá-los um pouco mais tarde.

A seção pública da classe contém métodos para criar a forma, definir e retornar seus parâmetros:

public:
//--- Create a form
   bool              CreateForm(const long chart_id,
                                const int wnd_num,
                                const string name,
                                const int x,
                                const int y,
                                const int w,
                                const int h,
                                const color colour,
                                const uchar opacity,
                                const bool movable=true,
                                const bool selectable=true);
                                
//--- Return the pointer to a canvas object
   CCanvas          *CanvasObj(void)                           { return &this.m_canvas;                           }
//--- Set (1) the form update frequency, (2) the movability flag and (3) selectability flag for interaction
   void              SetFrequency(const ulong value)           { this.m_pause.SetWaitingMSC(value);               }
   void              SetMovable(const bool flag)               { CGBaseObj::SetMovable(flag);                     }
   void              SetSelectable(const bool flag)            { CGBaseObj::SetSelectable(flag);                  }
//--- Update the form coordinates (shift the form)
   bool              Move(const int x,const int y,const bool redraw=false);
   
//--- Return the mouse status relative to the form
   ENUM_MOUSE_FORM_STATE MouseFormState(const int id,const long lparam,const double dparam,const string sparam);
//--- Return the flag of the clicked (1) left, (2) right, (3) middle, (4) first and (5) second additional mouse buttons
   bool              IsPressedButtonLeftOnly(void)             { return this.m_mouse.IsPressedButtonLeft();       }
   bool              IsPressedButtonRightOnly(void)            { return this.m_mouse.IsPressedButtonRight();      }
   bool              IsPressedButtonMiddleOnly(void)           { return this.m_mouse.IsPressedButtonMiddle();     }
   bool              IsPressedButtonX1Only(void)               { return this.m_mouse.IsPressedButtonX1();         }
   bool              IsPressedButtonX2Only(void)               { return this.m_mouse.IsPressedButtonX2();         }
//--- Return the flag of the pressed (1) Shift and (2) Ctrl key
   bool              IsPressedKeyShiftOnly(void)               { return this.m_mouse.IsPressedKeyShift();         }
   bool              IsPressedKeyCtrlOnly(void)                { return this.m_mouse.IsPressedKeyCtrl();          }
   
//--- Set the shift of the (1) left, (2) top, (3) right, (4) bottom edge of the active area relative to the form,
//--- (5) all shifts of the active area edges relative to the form and (6) the form opacity
   void              SetActiveAreaLeftShift(const int value)   { this.m_act_area_left=fabs(value);                }
   void              SetActiveAreaRightShift(const int value)  { this.m_act_area_right=fabs(value);               }
   void              SetActiveAreaTopShift(const int value)    { this.m_act_area_top=fabs(value);                 }
   void              SetActiveAreaBottomShift(const int value) { this.m_act_area_bottom=fabs(value);              }
   void              SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift);
   void              SetOpacity(const uchar value)             { this.m_opacity=value;                            }
//--- Return the coordinate (1) of the left, (2) right, (3) top and (4) bottom edge of the form active area
   int               ActiveAreaLeft(void)                const { return this.CoordX()+this.m_act_area_left;       }
   int               ActiveAreaRight(void)               const { return this.RightEdge()-this.m_act_area_right;   }
   int               ActiveAreaTop(void)                 const { return this.CoordY()+this.m_act_area_top;        }
   int               ActiveAreaBottom(void)              const { return this.BottomEdge()-this.m_act_area_bottom; }
//--- Return (1) the form opacity, coordinate (2) of the right and (3) bottom form edge
   uchar             Opacity(void)                       const { return this.m_opacity;                           }
   int               RightEdge(void)                     const { return CGBaseObj::RightEdge();                   }
   int               BottomEdge(void)                    const { return CGBaseObj::BottomEdge();                  }

//--- Event handler
   void              OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- Constructors/Destructor
                     CForm(const long chart_id,
                           const int wnd_num,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h,
                           const color colour,
                           const uchar opacity,
                           const bool movable=true,
                           const bool selectable=true);
                     CForm(){;}
                    ~CForm();
  };
//+------------------------------------------------------------------+

Vamos examinar mais de perto os métodos da classe.

No construtor paramétrico criamos um objeto-forma com parâmetros passados para o construtor:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CForm::CForm(const long chart_id,
             const int wnd_num,
             const string name,
             const int x,
             const int y,
             const int w,
             const int h,
             const color colour,
             const uchar opacity,
             const bool movable=true,
             const bool selectable=true) : m_act_area_bottom(0),
                                           m_act_area_left(0),
                                           m_act_area_right(0),
                                           m_act_area_top(0),
                                           m_mouse_state(0),
                                           m_mouse_state_flags(0)
                                          
  {
   if(this.CreateForm(chart_id,wnd_num,name,x,y,w,h,colour,opacity,movable,selectable))
     {
      this.m_shift_y=(int)::ChartGetInteger(chart_id,CHART_WINDOW_YDISTANCE,wnd_num);
      this.SetWindowNum(wnd_num);
      this.m_pause.SetWaitingMSC(PAUSE_FOR_CANV_UPDATE);
      this.m_pause.SetTimeBegin();
      this.m_mouse.SetChartID(chart_id);
      this.m_mouse.SetWindowNum(wnd_num);
      this.m_mouse.ResetAll();
      this.m_mouse_state_flags=0;
      CGBaseObj::SetMovable(movable);
      CGBaseObj::SetSelectable(selectable);
      this.SetOpacity(opacity);
     }
  }
//+------------------------------------------------------------------+

Aqui, primeiro, inicializamos todas as variáveis na lista de inicialização do construtor. Em seguida, chamamos o método para criar o formulário e, se o formulário for criado com sucesso, definimos os parâmetros passados para o construtor do objeto.

No destruidor da classe removemos o objeto gráfico criado:

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CForm::~CForm()
  {
   ::ObjectsDeleteAll(this.ChartID(),this.NameObj());
  }
//+------------------------------------------------------------------+


Método que cria um objeto gráfico-forma:

//+------------------------------------------------------------------+
//| Create the graphical form object                                 |
//+------------------------------------------------------------------+
bool CForm::CreateForm(const long chart_id,
                       const int wnd_num,
                       const string name,
                       const int x,
                       const int y,
                       const int w,
                       const int h,
                       const color colour,
                       const uchar opacity,
                       const bool movable=true,
                       const bool selectable=true)
  {
   if(this.m_canvas.CreateBitmapLabel(chart_id,wnd_num,name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE))
     {
      this.SetChartID(chart_id);
      this.SetWindowNum(wnd_num);
      this.SetNameObj(name);
      this.SetCoordX(x);
      this.SetCoordY(y);
      this.SetWidth(w);
      this.SetHeight(h);
      this.SetActiveAreaLeftShift(1);
      this.SetActiveAreaRightShift(1);
      this.SetActiveAreaTopShift(1);
      this.SetActiveAreaBottomShift(1);
      this.SetOpacity(opacity);
      this.SetMovable(movable);
      this.SetSelectable(selectable);
      this.m_canvas.Erase(::ColorToARGB(colour,this.Opacity()));
      this.m_canvas.Update();
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

Com ajuda do método CreateBitmapLabel() da classe CCanvas criamos um recurso gráfico que usa o identificador do gráfico e o número da subjanela (a segunda forma da chamada do método). Se o recurso gráfico foi criado com sucesso, definimos todos os parâmetros passados ao método para o objeto da forma. Preenchemos o formulário com a cor e definimos a opacidade usando o método Erase() e a exibição de mudanças na tela usando o método Update().

Quero esclarecer sobre o termo "opacidade" ou densidade de cor. A classe CCanvas permite definir a transparência dos objetos. Nesse caso, um valor de 0 é uma cor completamente transparente, um valor de 255 é uma cor completamente opaca. Na verdade, acaba por ser o oposto. Por esse motivo, decidi usar o termo "opacidade" (opacity), pois os valores de 0 a 255 correspondem exatamente a um aumento na densidade da cor de zero (totalmente transparente) a 255 (totalmente opaco).

Manipulador de eventos da classe CForm:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CForm::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Get the status of mouse buttons, Shift/Ctrl keys and the state of a mouse relative to the form
   ENUM_MOUSE_BUTT_KEY_STATE mouse_state=this.m_mouse.ButtKeyState(id,lparam,dparam,sparam);
   this.m_mouse_state=this.MouseFormState(id,lparam,dparam-this.m_shift_y,sparam);
//--- Initialize the difference between X and Y coordinates of the form and cursor
   static int diff_x=0;
   static int diff_y=0;
//--- In case of a chart change event, recalculate the shift by Y for the subwindow
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      this.m_shift_y=(int)::ChartGetInteger(this.ChartID(),CHART_WINDOW_YDISTANCE,this.WindowNum());
     }
//--- If the cursor is inside the form, disable chart scrolling, context menu and Crosshair tool
   if((this.m_mouse_state_flags & 0x0100)!=0)
     {
      ::ChartSetInteger(this.ChartID(),CHART_MOUSE_SCROLL,false);
      ::ChartSetInteger(this.ChartID(),CHART_CONTEXT_MENU,false);
      ::ChartSetInteger(this.ChartID(),CHART_CROSSHAIR_TOOL,false);
     }
//--- Otherwise, if the cursor is outside the form, allow chart scrolling, context menu and Crosshair tool
   else
     {
      ::ChartSetInteger(this.ChartID(),CHART_MOUSE_SCROLL,true);
      ::ChartSetInteger(this.ChartID(),CHART_CONTEXT_MENU,true);
      ::ChartSetInteger(this.ChartID(),CHART_CROSSHAIR_TOOL,true);
     }
//--- If the mouse movement event and the cursor are located in the form active area
   if(id==CHARTEVENT_MOUSE_MOVE && m_mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED)
     {
      //--- If only the left mouse button is being held and the form is moved,
      //--- set the new parameters of moving the form relative to the cursor
      if(IsPressedButtonLeftOnly() && this.Move(this.m_mouse.CoordX()-diff_x,this.m_mouse.CoordY()-diff_y))
        {
         diff_x=this.m_mouse.CoordX()-this.CoordX();
         diff_y=this.m_mouse.CoordY()-this.CoordY();
        }
     }
//--- In any other cases, set the parameters of shifting the form relative to the cursor
   else
     {
      diff_x=this.m_mouse.CoordX()-this.CoordX();
      diff_y=this.m_mouse.CoordY()-this.CoordY();
     }
//--- Test display of mouse states on the chart
   Comment(EnumToString(mouse_state),"\n",EnumToString(this.m_mouse_state));
  }
//+------------------------------------------------------------------+

Na listagem do código, toda a sua lógica é explicada nos comentários. O método deve ser chamado desde o manipulador OnChartEvent() padrão do programa, e tem exatamente os mesmos parâmetros.

Vou explicar sobre o cálculo alocado passado para o método MouseFormState(). Se nossa forma estiver localizada na janela do gráfico principal, o valor da variável m_shift_y será igual a zero e a expressão dparam-this.m_shift_y retornará a coordenada Y do cursor. Mas se o formulário estiver localizado na subjanela do gráfico, o deslocamento na variável m_shift_y será maior que zero - para ajustar a coordenada Y do cursor às coordenadas da subjanela. Assim, também precisamos passar o valor da coordenada Y com o deslocamento especificado na variável para os métodos de cálculo das coordenadas do cursor m_shift_y. Caso contrário, as coordenadas do objeto irão apontar mais alto do que realmente de acordo com o número de pixels do deslocamento especificado nesta variável.

Método que retorna a posição do cursor em relação ao formulário:

//+------------------------------------------------------------------+
//| Return the cursor position relative to the form                  |
//+------------------------------------------------------------------+
bool CForm::CursorInsideForm(const int x,const int y)
  {
   return(x>=this.CoordX() && x<this.RightEdge() && y>=this.CoordY() && y<=this.BottomEdge());
  }
//+------------------------------------------------------------------+

As coordenadas X e Y do cursor são passadas para o método.

Se

... então será retornado true - o cursor está dentro do objeto-forma.

Método que retorna a posição do cursor em relação à área ativa da forma:

//+------------------------------------------------------------------+
//| Return the cursor position relative to the form active area      |
//+------------------------------------------------------------------+
bool CForm::CursorInsideActiveArea(const int x,const int y)
  {
   return(x>=this.ActiveAreaLeft() && x<this.ActiveAreaRight() && y>=this.ActiveAreaTop() && y<=this.ActiveAreaBottom());
  }
//+------------------------------------------------------------------+

As coordenadas X e Y do cursor são passadas para o método.

Se

... então será retornado true - o cursor está dentro da zona ativa do objeto-forma.

Método que retorna o estado do mouse em relação à forma:

//+------------------------------------------------------------------+
//| Return 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)
  {
//--- Get the mouse status relative to the form, as well as the states of mouse buttons and Shift/Ctrl keys
   ENUM_MOUSE_FORM_STATE form_state=MOUSE_FORM_STATE_NONE;
   ENUM_MOUSE_BUTT_KEY_STATE state=this.MouseButtonKeyState(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(this.CursorInsideForm(m_mouse.CoordX(),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(CursorInsideActiveArea(m_mouse.CoordX(),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 one of the mouse buttons is clicked, check the cursor location in the active area and
      //--- return the appropriate value of the pressed key (in the active area or the form area)
      if((this.m_mouse_state_flags & 0x0001)!=0 || (this.m_mouse_state_flags & 0x0002)!=0 || (this.m_mouse_state_flags & 0x0010)!=0)
         form_state=((m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : MOUSE_FORM_STATE_INSIDE_PRESSED);
      //--- otherwise, check the cursor location in the active area and
      //--- return the appropriate value of the unpressed key (in the active area or the form area)
      else
         form_state=((m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : MOUSE_FORM_STATE_INSIDE_NOT_PRESSED);
     }
   return form_state;
  }
//+------------------------------------------------------------------+

Cada linha de código é explicada nos comentários de código. Resumindo: obtemos o estado do mouse pronto desde o objeto da classe de estado do mouse e o gravamos na variável m_mouse_state_flags. Além disso, dependendo da localização do cursor em relação à forma, simplesmente complementamos os sinalizadores de bits do estado do mouse com novos dados e retornamos o estado do mouse no formato da enumeração ENUM_MOUSE_FORM_STATE, que consideramos acima no início do artigo.

Método que atualiza as coordenadas da forma (desloca a forma no gráfico):

//+------------------------------------------------------------------+
//| Update the form coordinates                                      |
//+------------------------------------------------------------------+
bool CForm::Move(const int x,const int y,const bool redraw=false)
  {
//--- If the form is not movable, leave
   if(!this.Movable())
      return false;
//--- If new values are successfully set into graphical object properties
   if(::ObjectSetInteger(this.ChartID(),this.NameObj(),OBJPROP_XDISTANCE,x) &&
      ::ObjectSetInteger(this.ChartID(),this.NameObj(),OBJPROP_YDISTANCE,y))
     {
      //--- set the new values of X and Y coordinate properties
      this.SetCoordX(x);
      this.SetCoordY(y);
      //--- If the update flag is activated, redraw the chart.
      if(redraw)
         ::ChartRedraw(this.ChartID());
      //--- Return 'true'
      return true;
     }
//--- Something is wrong...
   return false;
  }
//+------------------------------------------------------------------+

As coordenadas são passadas para o método aonde desejamos mover o objeto-forma. Se conseguimos definir novos parâmetros de coordenadas para o objeto gráfico da forma, então escrevemos essas coordenadas nas propriedades do objeto e redesenhamos o gráfico apenas se o sinalizador de redesenho, que também é passado para o método, for marcado. O redesenho por valor de sinalizador é necessário para não redesenhar o gráfico muitas vezes se o objeto gráfico consistir em muitas formas. Nesse caso, precisamos primeiro mover todas as formas de um objeto e, em seguida, após cada forma receber novas coordenadas, atualizar o gráfico uma vez.

Método para estabelecer todos os deslocamentos da zona ativa em relação à forma:

//+------------------------------------------------------------------+
//| Set all shifts of the active area relative to the form           |
//+------------------------------------------------------------------+
void CForm::SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift)
  {
   this.SetActiveAreaLeftShift(left_shift);
   this.SetActiveAreaBottomShift(bottom_shift);
   this.SetActiveAreaRightShift(right_shift);
   this.SetActiveAreaTopShift(top_shift);
  }
//+------------------------------------------------------------------+

Temos métodos para separar os limites da zona ativa. Mas às vezes é necessário definir todos os limites numa chamada para um método. Isso é exatamente o que esse método faz - ele define novos valores do deslocamento dos limites da zona ativa a partir das bordas da forma por meio das chamadas dos métodos correspondentes.

Assim concluímos a criação da primeira versão do objeto-forma. Vamos testar e ver o resultado.

Teste

Para testar, vamos criar um objeto-forma no gráfico e tentar movê-lo com o cursor. Ao mesmo tempo, nos comentários do gráfico exibiremos o estado dos botões do mouse e as teclas Ctrl e Shift, bem como o estado do cursor em relação à forma e aos limites de sua zona ativa.

Na nova pasta \MQL5\Experts\TestDoEasy\Part73\ criamos um novo arquivo TestDoEasyPart73.mq5 para o Expert Advisor.

Ao criar o arquivo do EA, iremos indicar que precisamos de uma variável de entrada InpMovable com o tipo bool e o valor inicial true:


Em seguida, indicaremos que precisamos de um manipulador OnChartEvent() adicional:

Como resultado, teremos o seguinte EA:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart73.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- input parameters
input bool     InpMovable=true;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   
  }
//+------------------------------------------------------------------+

Ao arquivo do EA nexamos a classe do objeto-forma recém-criado e declaramos duas variáveis globais - prefixo do nome do objeto e objeto da classe CForm:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart73.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Objects\Graph\Form.mqh>
//--- input parameters
sinput   bool  InpMovable  = true;  // Movable flag
//--- global variables
string         prefix;
CForm          form;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+

No manipulador OnInit(), ativamos a permissão para enviar eventos para mover o cursor e rolar a roda do mouse, definimos o valor para o prefixo dos nomes dos objetos como (nome do arquivo) + "_" e criamos um objeto-forma no gráfico. Depois de criá-lo, definimos margens de 10 pixels para os limites da zona ativa:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set the permissions to send cursor movement and mouse scroll events
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
//--- Set EA global variables
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
//--- If the form is created, set an active area for it with the offset of 10 pixels from the edges
   if(form.CreateForm(ChartID(),0,prefix+"Form_01",300,20,100,70,clrSilver,200,InpMovable))
     {
      form.SetActiveAreaShift(10,10,10,10);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Agora resta chamar o manipulador OnChartEvent() do objeto-forma a partir do manipulador OnChartEvent() do Expert Advisor:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   form.OnChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

Vamos compilar o Expert Advisor e executá-lo no gráfico do símbolo:


Como podemos ver, o status dos botões e do cursor é exibido corretamente. O objeto-forma se move apenas se o agarramos com o mouse dentro de sua zona ativa.

Com o clique dos botões do meio e direito do mouse dentro da forma, o menu de contexto e a ferramenta de mira não são ativados. Mas também há um componente engraçado: se ativarmos a ferramenta de mira fora da janela e, em seguida, passarmos o mouse com ela (com o botão esquerdo do mouse pressionado) na área ativa da forma, ela começa a se deslocar. Esse comportamento é errado. Mas este é apenas o começo. Nos próximos artigos, aprimoraremos tudo e adicionaremos novas funcionalidades ao objeto-forma.

O que vem agora?

No próximo artigo, continuaremos a desenvolver a classe de objeto-forma.

Todos os arquivos da versão atual da biblioteca e o arquivo do EA de teste para MQL5 estão anexados abaixo. Você pode baixá-los e testar tudo sozinho.
Se você tiver perguntas, comentários e sugestões, poderá expressá-los nos comentários do artigo.

Complementos

*Artigo final da última série:

Outras classes na biblioteca DoEasy (Parte 72): rastreamento e fixação dos parâmetros de objetos-gráficos numa coleção