Gráficos na biblioteca DoEasy (Parte 73): objeto-forma de um elemento gráfico
Sumário
- Ideia
- Aprimorando as classes da biblioteca
- Classe de estados do mouse
- Classe base de objeto que compreende todos os elementos gráficos da biblioteca
- Classe de objeto-forma de elementos gráficos
- Teste
- O que vem agora?
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:
- 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.,
- 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:
- Se apenas o botão esquerdo for pressionado, o valor da variável será 1
- Se apenas o botão direito for pressionado, o valor da variável será 2
- Se os botões esquerdo e direito forem pressionados, o valor da variável será igual a 1 + 2 = 3
- Se apenas o botão esquerdo for pressionado e a tecla Shift for mantida pressionada, o valor da variável será 1 + 4 = 5
É 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:
- coordenadas da localização do objeto no gráfico;
- a largura e altura do elemento (tela) no qual serão localizados outros elementos de objetos compostos (que terão exatamente as mesmas propriedades comuns para todos os objetos);
- as coordenadas das bordas direita e inferior da tela (as bordas esquerda e superior correspondem às coordenadas);
- vários identificadores do objeto (tipo, nome e identificadores do gráfico e da subjanela);
- e alguns sinalizadores adicionais que especificam o comportamento do objeto ao interagir com ele.
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
- (a coordenada X do cursor é maior ou igual à coordenada X da forma e a coordenada X do cursor é menor ou igual à coordenada da borda direita da forma) e
- (a coordenada Y do cursor é maior ou igual à coordenada Y da forma e a coordenada Y do cursor é menor ou igual à coordenada da parte inferior da forma)
... 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
- (a coordenada X do cursor é maior ou igual à coordenada X da área ativa da forma e a coordenada X do cursor é menor ou igual à coordenada da borda direita da área ativa da forma) e
- (a coordenada Y do cursor é maior ou igual à coordenada Y da área ativa da forma e a coordenada Y do cursor é menor ou igual à coordenada do limite inferior da área ativa da forma)
... 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.
*Artigo final da última série:
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/9442
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso