Melhoramos o trabalho com Painéis, adicionando transparência, alterando a cor do plano de fundo e herdando da CAppDialog/CWndClient

4 julho 2018, 15:29
Vladimir Karputov
0
562

Sumário

Introdução

Os painéis baseados na classe CAppDialog não possuem métodos suficientes para acessar diretamente as propriedades dos controles que compõem o painel, entre eles "Cor do plano de fundo" e "Cor do quadro". É por isso que todos os painéis são criados igualmente em cinza. 

Não tendo como alterar a cor dos controles, é impossível conceber ideias de design. Claro, você pode resolver esse problema herdando e adicionando seus métodos. Mas, para isso, será necessário fazer algumas correções no código gerado. Será que existe uma maneira mais simples e rápida de acessar as propriedades "Cor do plano de fundo" e "Cor do quadro" para os controles do painel?


Painel transparente ao ser movido

Primeiro, vou mostrar o que pode ser feito para o painel baseado na classe CAppDialog (este é um exemplo do código "Live panel.mq5").

Live panel

Esta animação mostra que, se você mover o painel, apenas permanece a borda externa. Durante o movimento, a borda externa muda de cor aleatoriamente. Concluído o movimento, o formulário se torna normal novamente, pois, na área de trabalho, aparece o sombreamento da tela de fundo.

Como é feito isso

Todo o trabalho é feito em torno da classe CDialog. Ela está localizada no arquivo [data folder]\MQL5\Include\Controls\Dialog.mqh

Deixe-me lembrá-lo de quais objetos compõem o painel (estes objetos são declarados na classe CDialog na seção private) e de como eles são incorporados visualmente como elementos gráficos:

//+------------------------------------------------------------------+
//| Class CDialog                                                    |
//| Usage: base class to create dialog boxes                         |
//|             and indicator panels                                 |
//+------------------------------------------------------------------+
class CDialog : public CWndContainer
  {
private:
   //--- dependent controls
   CPanel            m_white_border;        // the "white border" object
   CPanel            m_background;          // the background object
   CEdit             m_caption;             // the window title object
   CBmpButton        m_button_close;        // the "Close" button object
   CWndClient        m_client_area;         // the client area object

protected:

CDialog objects

Para tornar o painel transparente ao arrastá-lo, você precisa considerar quatro pontos.

1. Você deverá prestar atenção aos elementos gráficos "Border" e "Back" (criados pelos objetos m_white_borderm_background da classe CPanel) e o elemento "Client" (criado pelo objeto m_client_area da classe CWndClient). Adicionalmente, poderá ver como eles são criados nas funções CDialog::CreateWhiteBorder, CDialog::CreateBackground e CDialog::CreateClientArea.

2. Ao ser criado, o painel recebe um nome único, isto é, um prefixo numérico que se põe antes dos nomes de todos os objetos gráficos (na figura abaixo o prefixo é 03082):

Objects

3. Na classe CDialog, existem três manipuladores de arrastar e soltar:

 Processamento de arrastar e soltar
 OnDialogDragStart  Manipulador virtual do evento "DialogDragStart" do controle
 OnDialogDragProcess  Manipulador virtual do evento "DialogDragProcess" do controle
 OnDialogDragEnd  Manipulador virtual do evento "DialogDragEnd" do controle

4. O segredo do artigo "Como criar um painel gráfico de qualquer nível de complexidade" está em percorrer os objetos do painel (neste caso, ExtDialog é o objeto de classe do painel):

   int total=ExtDialog.ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=ExtDialog.Control(i);
...

Por que, ao percorrer o ciclo, o ponteiro para o objeto obj é declarado com o tipo CWnd?

Porque CWnd é a classe base da qual todas as outras classes filho se originam:

Agora você pode avançar para a implementação

Para trabalhar com cor, você precisa de duas macros, e, para interceptar o movimento do painel, você precisa redefinir três funções da classe CDialog:

//+------------------------------------------------------------------+
//|                                                   Live panel.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                               http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.000"
#property description "O painel muda sua transparência ao se mover"
#include <Controls\Dialog.mqh>
#define XRGB(r,g,b)    (0xFF000000|(uchar(r)<<16)|(uchar(g)<<8)|uchar(b))
#define GETRGB(clr)    ((clr)&0xFFFFFF)
//+------------------------------------------------------------------+
//| Class CLivePanel                                                 |
//| Usage: main dialog of the Controls application                   |
//+------------------------------------------------------------------+
class CLivePanel : public CAppDialog
  {
public:
                     CLivePanel(void);
                    ~CLivePanel(void);
   //--- create
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   //--- handlers of drag
   virtual bool      OnDialogDragStart(void);
   virtual bool      OnDialogDragProcess(void);
   virtual bool      OnDialogDragEnd(void);

  };

Embora pule a parte padrão do trabalho com o painel (criação, exclusão e transferência de eventos), vou me debruçar sobre os manipuladores do movimento em mais detalhes.

Manipulador "OnDialogDragStart": início do movimento do painel

Você obtém o prefixo, a seguir, no ciclo percorre todos os objetos do painel e procura o nome "Border", "Back" ou "Client" com prefixo:

//+------------------------------------------------------------------+
//| Start dragging the dialog box                                    |
//+------------------------------------------------------------------+
bool CLivePanel::OnDialogDragStart(void)
  {
   string prefix=Name();
   int total=ExtDialog.ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=ExtDialog.Control(i);
      string name=obj.Name();
      //---
      if(name==prefix+"Border")
        {
         CPanel *panel=(CPanel*) obj;
         panel.ColorBackground(clrNONE);
         ChartRedraw();
        }
      if(name==prefix+"Back")
        {
         CPanel *panel=(CPanel*) obj;
         panel.ColorBackground(clrNONE);
         ChartRedraw();
        }
      if(name==prefix+"Client")
        {
         CWndClient *wndclient=(CWndClient*) obj;
         wndclient.ColorBackground(clrNONE);
         wndclient.ColorBorder(clrNONE);
         ChartRedraw();
        }
     }
   return(CDialog::OnDialogDragStart());
  }

Encontrados os objetos, exclua a cor do plano de fundo (método "ColorBackground") e o quadro (método "ColorBorder"), usando a cor clrNONE. Desta forma, é implementada a transparência do formulário.

Manipulador "OnDialogDragProcess": continuação do movimento do painel

Procure apenas um objeto, nomeadamente "Back", e mude dinamicamente sua cor (usando as duas macros XRGB e GETRGB):

//+------------------------------------------------------------------+
//| Continue dragging the dialog box                                 |
//+------------------------------------------------------------------+
bool CLivePanel::OnDialogDragProcess(void)
  {
   string prefix=Name();
   int total=ExtDialog.ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=ExtDialog.Control(i);
      string name=obj.Name();
      if(name==prefix+"Back")
        {
         CPanel *panel=(CPanel*) obj;
         color clr=(color)GETRGB(XRGB(rand()%255,rand()%255,rand()%255));
         panel.ColorBorder(clr);
         ChartRedraw();
        }
     }
   return(CDialog::OnDialogDragProcess());
  }

Manipulador "OnDialogDragEnd": final do movimento do painel

Redefina a cor de fundo e os quadros de objeto "Border", "Back" ou "Client":

//+------------------------------------------------------------------+
//| End dragging the dialog box                                      |
//+------------------------------------------------------------------+
bool CLivePanel::OnDialogDragEnd(void)
  {
   string prefix=Name();
   int total=ExtDialog.ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=ExtDialog.Control(i);
      string name=obj.Name();
      //---
      if(name==prefix+"Border")
        {
         CPanel *panel=(CPanel*) obj;
         panel.ColorBackground(CONTROLS_DIALOG_COLOR_BG);
         panel.ColorBorder(CONTROLS_DIALOG_COLOR_BORDER_LIGHT);
         ChartRedraw();
        }
      if(name==prefix+"Back")
        {
         CPanel *panel=(CPanel*) obj;
         panel.ColorBackground(CONTROLS_DIALOG_COLOR_BG);
         color border=(m_panel_flag) ? CONTROLS_DIALOG_COLOR_BG : CONTROLS_DIALOG_COLOR_BORDER_DARK;
         panel.ColorBorder(border);
         ChartRedraw();
        }
      if(name==prefix+"Client")
        {
         CWndClient *wndclient=(CWndClient*) obj;
         wndclient.ColorBackground(CONTROLS_DIALOG_COLOR_CLIENT_BG);
         wndclient.ColorBorder(CONTROLS_DIALOG_COLOR_CLIENT_BORDER);
         ChartRedraw();
        }
     }
   return(CDialog::OnDialogDragEnd());
  }

Adicione um botão ao painel e faça com que se torne transparente ao mover o painel

Este exemplo está contido no código "Live panel and Button.mq5"

Para trabalhar com o botão, primeiro, é preciso conectar ao vosso EA a classe dos botões e adicionar os macros responsáveis ​​por seu posicionamento e tamanho:

#property description "O painel muda sua transparência ao se mover,"
#property description " enquanto o botão permanece da mesma cor"
#include <Controls\Dialog.mqh>
#include <Controls\Button.mqh>
#define XRGB(r,g,b)    (0xFF000000|(uchar(r)<<16)|(uchar(g)<<8)|uchar(b))
#define GETRGB(clr)    ((clr)&0xFFFFFF)
//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
//--- indents and gaps
#define INDENT_LEFT                         (11)      // indent from left (with allowance for border width)
#define INDENT_TOP                          (11)      // indent from top (with allowance for border width)
#define CONTROLS_GAP_X                      (5)       // gap by X coordinate
//--- for buttons
#define BUTTON_WIDTH                        (100)     // size by X coordinate
#define BUTTON_HEIGHT                       (20)      // size by Y coordinate
//+------------------------------------------------------------------+
//| Class CLivePanelAndButton                                        |
//| Usage: main dialog of the Controls application                   |
//+------------------------------------------------------------------+
class CLivePanelAndButton : public CAppDialog

Além disso, para poder trabalhar com o botão, você precisa declarar um objeto da classe CButton:

//+------------------------------------------------------------------+
//| Class CLivePanelAndButton                                        |
//| Usage: main dialog of the Controls application                   |
//+------------------------------------------------------------------+
class CLivePanelAndButton : public CAppDialog
  {
private:
   CButton           m_button1;                       // the button object

public:
                     CLivePanelAndButton(void);

e o procedimento para criar um botão:

   virtual bool      OnDialogDragEnd(void);

protected:
   //--- create dependent controls
   bool              CreateButton1(void);

  };

Código "CreateButton1" — após a criação do botão não se esqueça de adicionar o botão ao painel:

//+------------------------------------------------------------------+
//| Create the "Button1" button                                      |
//+------------------------------------------------------------------+
bool CLivePanelAndButton::CreateButton1(void)
  {
//--- coordinates
   int x1=INDENT_LEFT;        // x1            = 11  pixels
   int y1=INDENT_TOP;         // y1            = 11  pixels
   int x2=x1+BUTTON_WIDTH;    // x2 = 11 + 100 = 111 pixels
   int y2=y1+BUTTON_HEIGHT;   // y2 = 11 + 20  = 32  pixels
//--- create
   if(!m_button1.Create(0,"Button1",0,x1,y1,x2,y2))
      return(false);
   if(!m_button1.Text("Button1"))
      return(false);
   if(!Add(m_button1))
      return(false);
//--- succeed
   return(true);
  }

Aqui está o resultado, se você adicionar um botão ao painel do exemplo acima:

Live panel and Button

Como você pode ver, quando move o painel, ele fica transparente, mas o controle adicionado - o botão - permanece opaco. Aqui há variantes de escrita de código para amadores, nomeadamente um plano de fundo transparente ao se mover e um botão que vira transparente ao mesmo tempo. Vamos trabalhar com segunda variante, quer dizer, vamos deixar o botão transparente ao mover o painel.

Torne o botão também transparente ao mover o painel

Faça isso com o código "Live panel and transparent Button.mq5".

Quando ao painel é adicionado um controle simples ou combinado, o objeto que o cria é adicionado ao objeto m_client_area (deixe-me lembrá-lo que o objeto m_client_area é atualizado na classe CDialog). Em consequência, ao ser detectado um movimento do painel, é necessário organizar uma iteração de ciclo para todos os objetos adicionados ao m_client_area. Fazer isso é conveniente no primeiro manipulador — "OnDialogDragStart" (início do movimento do painel):

//+------------------------------------------------------------------+
//| Start dragging the dialog box                                    |
//+------------------------------------------------------------------+
bool CLivePaneTransparentButton::OnDialogDragStart(void)
  {
   string prefix=Name();
   int total=ExtDialog.ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=ExtDialog.Control(i);
      string name=obj.Name();
      //---
      if(name==prefix+"Border")
        {
         CPanel *panel=(CPanel*) obj;
         panel.ColorBackground(clrNONE);
         ChartRedraw();
        }
      if(name==prefix+"Back")
        {
         CPanel *panel=(CPanel*) obj;
         panel.ColorBackground(clrNONE);
         ChartRedraw();
        }
      if(name==prefix+"Client")
        {
         CWndClient *wndclient=(CWndClient*) obj;
         wndclient.ColorBackground(clrNONE);
         wndclient.ColorBorder(clrNONE);
         //---
         int client_total=wndclient.ControlsTotal();
         for(int j=0;j<client_total;j++)
           {
            CWnd*client_obj=wndclient.Control(j);
            string client_name=client_obj.Name();
            if(client_name=="Button1")
              {
               CButton *button=(CButton*) client_obj;
               button.ColorBackground(clrNONE);
               ChartRedraw();
              }
           }
         ChartRedraw();
        }
     }
   return(CDialog::OnDialogDragStart());
  }

Quando o movimento é detectado pela primeira vez, o painel e o botão se tornam transparentes.

O segundo manipulador — "OnDialogDragProcess" (continuação do movimento do painel) não será alterado. Mas o terceiro — "OnDialogDragEnd" (final do movimento do painel) deve ser alterado, pois é necessário devolver a cor para o botão:

//+------------------------------------------------------------------+
//| End dragging the dialog box                                      |
//+------------------------------------------------------------------+
bool CLivePaneTransparentButton::OnDialogDragEnd(void)
  {
   string prefix=Name();
   int total=ExtDialog.ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=ExtDialog.Control(i);
      string name=obj.Name();
      //---
      if(name==prefix+"Border")
        {
         CPanel *panel=(CPanel*) obj;
         panel.ColorBackground(CONTROLS_DIALOG_COLOR_BG);
         panel.ColorBorder(CONTROLS_DIALOG_COLOR_BORDER_LIGHT);
         ChartRedraw();
        }
      if(name==prefix+"Back")
        {
         CPanel *panel=(CPanel*) obj;
         panel.ColorBackground(CONTROLS_DIALOG_COLOR_BG);
         color border=(m_panel_flag) ? CONTROLS_DIALOG_COLOR_BG : CONTROLS_DIALOG_COLOR_BORDER_DARK;
         panel.ColorBorder(border);
         ChartRedraw();
        }
      if(name==prefix+"Client")
        {
         CWndClient *wndclient=(CWndClient*) obj;
         wndclient.ColorBackground(CONTROLS_DIALOG_COLOR_CLIENT_BG);
         wndclient.ColorBorder(CONTROLS_DIALOG_COLOR_CLIENT_BORDER);
         //---
         int client_total=wndclient.ControlsTotal();
         for(int j=0;j<client_total;j++)
           {
            CWnd*client_obj=wndclient.Control(j);
            string client_name=client_obj.Name();
            if(client_name=="Button1")
              {
               CButton *button=(CButton*) client_obj;
               button.ColorBackground(CONTROLS_BUTTON_COLOR_BG);
               ChartRedraw();
              }
           }
         ChartRedraw();
        }
     }
   return(CDialog::OnDialogDragEnd());
  }

Agora o código "Live panel and transparent Button.mq5" implementa totalmente a mudança de cor do painel e do botão ao mover o painel:

Live panel and transparent Button


Adicione dois botões ao painel: controle a cor de fundo do painel e a cor da barra de título

Este exemplo está contido no código "Live panel and button Clicks.mq5", e foi criado com base no código anterior "Live panel and transparent Button.mq5". Agora, em vez dos eventos de movimento do painel, você "capturará" os eventos de clique de botões. Também no painel haverá dois botões: um botão é responsável por alterar a cor de fundo do painel, o outro - por mudar a cor da sua barra de título.

Para capturar eventos associados a controles adicionados ao painel, você deve declarar um manipulador de eventos e escrever o manipulador em si:

//+------------------------------------------------------------------+
//| Class CLivePaneButtonClicks                                      |
//| Usage: main dialog of the Controls application                   |
//+------------------------------------------------------------------+
class CLivePaneButtonClicks : public CAppDialog
  {
private:
   CButton           m_button1;                       // the button object
   CButton           m_button2;                       // the button object

public:
                     CLivePaneButtonClicks(void);
                    ~CLivePaneButtonClicks(void);
   //--- create
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   //--- chart event handler
   virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);

protected:
   //--- create dependent controls
   bool              CreateButton1(void);
   bool              CreateButton2(void);
   //--- handlers of the dependent controls events
   void              OnClickButton1(void);
   void              OnClickButton2(void);

  };
//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CLivePaneButtonClicks)
ON_EVENT(ON_CLICK,m_button1,OnClickButton1)
ON_EVENT(ON_CLICK,m_button2,OnClickButton2)
EVENT_MAP_END(CAppDialog)
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+

O manipulador é apenas o método OnEvent escrito por macros do bloco "Events" e do bloco "Macro of event handling map" do arquivo "Defines.mqh" (para detalhes, veja o artigo Como criar um painel gráfico de qualquer complexidade).

O manipulador é lido assim:

  • evento OnEvent para a classe CLivePaneButtonClicks:
    • se houver um clique no controle "m_button1" — chame o manipulador "OnClickButton1"
    • se houver um clique no controle "m_button2" — chame o manipulador "OnClickButton2"
  • retorno do evento OnEvent para a classe pai CAppDialog

Manipuladores "OnClickButton1" e "OnClickButton2"

Em ambos os manipuladores, é organizada a iteração de todos os objetos que compõem o painel (eles estão listados no ponto Como é feito isso), neste caso, a iteração de todos os objetos do painel "ExtDialog". Como resultado, é chamado o método CWndContainer::ControlsTotal()

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CLivePaneButtonClicks::OnClickButton1(void)
  {
   string prefix=Name();
   int total=ExtDialog.ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=ExtDialog.Control(i);
      string name=obj.Name();
      //---
      if(name==prefix+"Client")
        {
         CWndClient *wndclient=(CWndClient*) obj;
         color clr=(color)GETRGB(XRGB(rand()%255,rand()%255,rand()%255));
         wndclient.ColorBackground(clr);
         ChartRedraw();
         return;
        }
     }
  }
//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CLivePaneButtonClicks::OnClickButton2(void)
  {
   string prefix=Name();
   int total=ExtDialog.ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=ExtDialog.Control(i);
      string name=obj.Name();
      //---
      if(name==prefix+"Caption")
        {
         CEdit *edit=(CEdit*) obj;
         color clr=(color)GETRGB(XRGB(rand()%255,rand()%255,rand()%255));
         edit.ColorBackground(clr);
         ChartRedraw();
         return;
        }
     }
  }

No manipulador "OnClickButton1", procure o objeto de área de cliente com o nome prefix+"Client" (este será o objeto da classe CWndClient), enquanto no manipulador "OnClickButton2" - o objeto de barra de título com o nome prefix+"Caption" (este será o objeto CEdit). Nos dois casos, selecione aleatoriamente a cor de fundo para os objetos encontrados. Veja o resultado:

Live panel and button Clicks


Herde da CAppDialog

O esquema de implementação difere do usado nos exemplos da biblioteca padrão (\MQL5\Experts\Examples\Controls\ e \MQL5\Indicators\Examples\Panels\SimplePanel\), quer dizer, no arquivo de plug-in "MyAppDialog.mqh", é criada a classe "CMyAppDialog" herdada da CAppDialog. A classe implementa apenas três métodos para gerenciar a cor do formulário e da barra de título. Ela não tem métodos para criar os controles adicionados, o manipulador OnEvent e os manipuladores de clique de botões (controles adicionados). 

Os objetos de classe CButton (controles adicionados, dois botões) são criados no arquivo executável "MyAppWindow.mq5". Também no arquivo "MyAppWindow.mq5", no manipulador OnChartEvent, são capturados os eventos de clique de botões e são chamados os métodos de mudança de cor.

MyAppDialog.mqh

Em vossa classe, adicione três métodos: 

  • CMyAppDialog::ColorBackground — definir a cor de fundo,
  • void CMyAppDialog::ColorCaption — definir a cor da barra de título,
  • color CMyAppDialog::ColorCaption — obter a cor da barra de título.

O algoritmo para acessar as propriedades dos objetos é semelhante aos códigos anteriores: no ciclo iteração de todos os objetos que compõem o painel e associação do nome do objeto. Há mais um método em falta, nomeadamente obter a cor de fundo. Porém, isso não pode ser feito por meios simples.

//+------------------------------------------------------------------+
//|                                                  MyAppDialog.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                               http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property description "Classe CMyAppDialog herdada da CAppDialog"
#property description "Métodos adicionados para definir a cor de fundo e da barra de título"
#include <Controls\Dialog.mqh>
//+------------------------------------------------------------------+
//| Class CLivePanelTwoButtons                                       |
//| Usage: main dialog of the Controls application                   |
//+------------------------------------------------------------------+
class CMyAppDialog : public CAppDialog
  {
public:
   void              ColorBackground(const color clr);
   color             ColorCaption(void);
   void              ColorCaption(const color clr);
//--- construtor e destrutor   
public:
                     CMyAppDialog(void){};
                    ~CMyAppDialog(void){};
  };
//+------------------------------------------------------------------+
//| Define a cor de plano de fundo                                          |
//+------------------------------------------------------------------+
void CMyAppDialog::ColorBackground(const color clr)
  {
   string prefix=Name();
   int total=ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=Control(i);
      string name=obj.Name();
      //---
      if(name==prefix+"Client")
        {
         CWndClient *wndclient=(CWndClient*) obj;
         wndclient.ColorBackground(clr);
         ChartRedraw();
         return;
        }
     }
//---     
  } 
//+------------------------------------------------------------------+
//| Define a cor de barra de título                                     |
//+------------------------------------------------------------------+
void CMyAppDialog::ColorCaption(const color clr)
  {
   string prefix=Name();
   int total=ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=Control(i);
      string name=obj.Name();
      //---
      if(name==prefix+"Caption")
        {
         CEdit *edit=(CEdit*) obj;
         edit.ColorBackground(clr);
         ChartRedraw();
         return;
        }
     }
//---     
  }  
//+------------------------------------------------------------------+
//| Obtém a cor de barra de fundo                                          |
//+------------------------------------------------------------------+
color CMyAppDialog::ColorCaption(void)
  {
   string prefix=Name();
   int total=ControlsTotal();
   color clr=clrNONE;
   for(int i=0;i<total;i++)
     {
      CWnd*obj=Control(i);
      string name=obj.Name();
      //---
      if(name==prefix+"Caption")
        {
         CEdit *edit=(CEdit*) obj;
         clr=edit.ColorBackground(clr);
         return clr;
        }
     }
//--- retorne a cor
   return clr;     
  }  
//+------------------------------------------------------------------+


MyAppWindow.mq5

 "MyAppWindow.mq5" — arquivo executável. Neste arquivo, para gerar cores, são declaradas as macros XRGB e GETRGB. Em OnInit, é criado o painel, são adicionados os botões e é iniciado o próprio painel.

//+------------------------------------------------------------------+
//|                                                  MyAppWindow.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                               http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property description "Aplicativo MyAppWindow baseado na classe CMyAppDialog"
#property description "Adicionados os botões para definir a cor de plano de fundo e da barra de título"
#include "MyAppDialog.mqh"
#include <Controls\Button.mqh>
//--- macros para trabalhar com cores
#define XRGB(r,g,b)    (0xFF000000|(uchar(r)<<16)|(uchar(g)<<8)|uchar(b))
#define GETRGB(clr)    ((clr)&0xFFFFFF)
//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
//--- indents and gaps
#define INDENT_LEFT                         (11)      // indent from left (with allowance for border width)
#define INDENT_TOP                          (11)      // indent from top (with allowance for border width)
#define CONTROLS_GAP_X                      (5)       // gap by X coordinate
//--- for buttons
#define BUTTON_WIDTH                        (100)     // size by X coordinate
#define BUTTON_HEIGHT                       (20)      // size by Y coordinate
//---
CMyAppDialog         AppWindow;
CButton              m_button1;                       // the button object
CButton              m_button2;                       // the button object
//+------------------------------------------------------------------+
//| Expert initialization function                                     |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create application dialog
   if(!AppWindow.Create(0,"CMyAppDialog: change Back and Caption colors",0,40,40,380,344))
      return(INIT_FAILED);
//--- create dependent controls
   if(!CreateBackButton())
      return(false);
   if(!CreateCaptionButton())
      return(false);
//--- run application
   AppWindow.Run();
//--- succeed
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   Comment("");
//--- destroy dialog
   AppWindow.Destroy(reason);
  }
//+------------------------------------------------------------------+
//| Expert chart event function                                      |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // event ID  
                  const long& lparam,   // event parameter of the long type
                  const double& dparam, // event parameter of the double type
                  const string& sparam) // event parameter of the string type
  {
//--- primeiro, processe os eventos de botões 
   if((StringFind(sparam,"Back")!=-1) && id==(CHARTEVENT_OBJECT_CLICK))
     {
      Print(__FUNCSIG__," sparam=",sparam);
      AppWindow.ColorBackground(GetRandomColor());
     }
   if((StringFind(sparam,"Caption")!=-1) && id==(CHARTEVENT_OBJECT_CLICK))
     {
      Print(__FUNCSIG__," sparam=",sparam);
      AppWindow.ColorCaption(GetRandomColor());
     }
//--- e agora o método da classe CMyAppDialog processa o resto dos eventos   
   AppWindow.ChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+
//| Create the "Button1" button                                      |
//+------------------------------------------------------------------+
bool CreateBackButton(void)
  {
//--- coordinates
   int x1=INDENT_LEFT;        // x1            = 11  pixels
   int y1=INDENT_TOP;         // y1            = 11  pixels
   int x2=x1+BUTTON_WIDTH;    // x2 = 11 + 100 = 111 pixels
   int y2=y1+BUTTON_HEIGHT;   // y2 = 11 + 20  = 32  pixels
//--- create
   if(!m_button1.Create(0,"Back",0,x1,y1,x2,y2))
      return(false);
   if(!m_button1.Text("Back"))
      return(false);
   if(!AppWindow.Add(m_button1))
      return(false);
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Create the "Button2"                                             |
//+------------------------------------------------------------------+
bool CreateCaptionButton(void)
  {
//--- coordinates
   int x1=INDENT_LEFT+2*(BUTTON_WIDTH+CONTROLS_GAP_X);   // x1 = 11  + 2 * (100 + 5) = 221 pixels
   int y1=INDENT_TOP;                                    // y1                       = 11  pixels
   int x2=x1+BUTTON_WIDTH;                               // x2 = 221 + 100           = 321 pixels
   int y2=y1+BUTTON_HEIGHT;                              // y2 = 11  + 20            = 31  pixels
//--- create
   if(!m_button2.Create(0,"Caption",0,x1,y1,x2,y2))
      return(false);
   if(!m_button2.Text("Caption"))
      return(false);
   if(!AppWindow.Add(m_button2))
      return(false);
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Obtém a cor aleatoriamente                                  |
//+------------------------------------------------------------------+
color GetRandomColor()
  {
   color clr=(color)GETRGB(XRGB(rand()%255,rand()%255,rand()%255));
   return clr;
  }
//+------------------------------------------------------------------+

Também no arquivo principal está o manipulador de eventos OnChartEvent. Ele envia todos os eventos para o painel, mas, ao detectar um evento de clique, (CHARTEVENT_OBJECT_CLICK) chama os métodos de classe do painel (AppWindow.ColorBackground ou AppWindow.ColorCaption).

Assim funciona a combinação de dois arquivos: o principal mq5 que inicia o mq5 e o mqh a ser incluído, no qual está localizada a classe do painel.


Herde da CWndClient

Neste exemplo, você vai analisar a herança da CWndClient: crie o objeto de classe "CWndClient". Este objeto conterá esta funcionalidade:

  • criação do objeto "CMyWndClient" — área de cliente do painel;
  • criação do objeto para adicionar dois botões à área de cliente;
  • manipulador de cliques de botões adicionados (mudança da cor de fundo da área de cliente e da cor da barra de título do painel);
  • além disso, para a área de cliente, será incluída uma barra de rolagem horizontal (lembre-se de que a classe CWndClient é o controle combinado "Área de cliente" e é a classe base para criar as áreas de barra de rolagem);
  • adicionalmente, também haverá manipuladores de clique de barra de rolagem horizontal (movimento dos botões adicionados sobre a área de cliente).

Dê uma olhada nos arquivos MyWndClient.mq5 e MyWndClient.mqh.

MyWndClient.mq5

Diferentemente dos exemplos da biblioteca padrão (\MQL5\Experts\Examples\Controls\ e \MQL5\Indicators\Examples\Panels\SimplePanel\), no arquivo incluído está a classe herdada da classe CWndClient 10 — área de cliente. O ciclo completo de criação de um painel se parece com isto:

  1. É criado o painel (objeto AppWindow da classe CAppDialog chama o método Create).
  2. É criada vossa área de cliente (objeto ClientArea da classe CMyWndClient do arquivo incluído MyWndClient.mqh chama o método Create).
  3. A área de cliente criada é adicionada ao painel (na parte de acima da área de cliente do painel).
CAppDialog           AppWindow;
CMyWndClient         ClientArea;
//+------------------------------------------------------------------+
//| Expert initialization function                                     |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create application dialog
   bool result_create=false;
   if(!InpTwoButtonsVisible)
     {
      //--- after creation of the panel, one button will be visible
      result_create=AppWindow.Create(0,"CAppDialog with CMyWndClient",0,40,40,360,344);
     }
   else
     {
      //--- after creation of the panel, will two buttons are visible
      result_create=AppWindow.Create(0,"CAppDialog with CMyWndClient",0,40,40,420,344);
     }
   if(!result_create)
      return(INIT_FAILED);
//--- create the panel
   PrintFormat("Application Rect: Height=%d  Width=%d",AppWindow.Rect().Height(),AppWindow.Rect().Width());
   CRect inner_rect=ClientArea.GetClientRect(GetPointer(AppWindow));
   PrintFormat("Client Area: Height=%d  Width=%d",inner_rect.Height(),inner_rect.Width());
   ClientArea.Create(0,"MyWndClient",0,0,0,inner_rect.Width(),inner_rect.Height());
   AppWindow.Add(ClientArea);
//--- defina o proprietário
   ClientArea.SetOwner(GetPointer(AppWindow));
//--- ocultar o invisível
   ClientArea.HideInvisble(HideInvisble);
//--- run application
   AppWindow.Run();
//--- succeed
   return(INIT_SUCCEEDED);
  }

O arquivo executável possui dois parâmetros de entrada:

  • largura do painel — criação de um painel de largura normal ou de um painel largo;
  • ocultar o invisível — exibir ou ocultar controles ocultos.

Ao criar o painel, é levado em consideração apenas um parâmetro de entrada, nomeadamente a "largura do painel". É assim que você cria um painel de largura normal:

//+------------------------------------------------------------------+
//| Expert initialization function                                     |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create application dialog
   bool result_create=false;
   if(!InpTwoButtonsVisible)
     {
      //--- after creation of the panel, one button will be visible
      result_create=AppWindow.Create(0,"CAppDialog with CMyWndClient",0,40,40,360,344);
     }
   else
     {
      //--- after creation of the panel, will two buttons are visible
      result_create=AppWindow.Create(0,"CAppDialog with CMyWndClient",0,40,40,420,344);
     }
   if(!result_create)
      return(INIT_FAILED);
//--- create the panel

MyWndClient normal width panel

e assim, um painel largo:

//+------------------------------------------------------------------+
//| Expert initialization function                                     |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create application dialog
   bool result_create=false;
   if(!InpTwoButtonsVisible)
     {
      //--- after creation of the panel, one button will be visible
      result_create=AppWindow.Create(0,"CAppDialog with CMyWndClient",0,40,40,360,344);
     }
   else
     {
      //--- after creation of the panel, will two buttons are visible
      result_create=AppWindow.Create(0,"CAppDialog with CMyWndClient",0,40,40,420,344);
     }
   if(!result_create)
      return(INIT_FAILED);
//--- create the panel

MyWndClient wide panel

O código de criação do painel difere apenas na largura definida (360 e 420); essa largura não é levada em conta ao criar dois botões. Compare as duas últimas imagens. Agora coloque uma sobre a outra:

CMyWndClient imposition

Veja que o botão "Caption" está ligeiramente fora da borda do painel de largura normal, isto é, fora da borda da área de cliente. Quando o controle adicionado sai das bordas, ele é forçosamente oculto do usuário (mas não excluído ou destruído). O procedimento para determinar se é necessário ocultar o controle é inicializado quando ele é adicionado à área do cliente, quer dizer, ao chamar o método CWndContainer::Add. Em vosso exemplo, o método Add é chamado em AddButton2:

//+------------------------------------------------------------------+
//| Create the "Button2"                                             |
//+------------------------------------------------------------------+
bool CMyWndClient::AddButton2(void)
  {
...
   if(!Add(m_button2))
     {
      Print("Add(m_button2) --> false");
      return(false);
     }
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Add control to the group (by reference)                          |
//+------------------------------------------------------------------+
bool CWndContainer::Add(CWnd &control)
  {
//--- add by pointer
   return(Add((CWnd*)GetPointer(control)));
  }

e, em consequência, chamado o mais importante, pois aqui você determina se esconde ou não o controle

//+------------------------------------------------------------------+
//| Add control to the group (by pointer)                            |
//+------------------------------------------------------------------+
bool CWndContainer::Add(CWnd *control)
  {
//--- check of pointer
   if(control==NULL)
      return(false);
//--- correct the coordinates of added control
   control.Shift(Left(),Top());
 //--- "projecting" the group flag "visibility" to the added element
   if(IS_VISIBLE && control.IsVisible())
     {
      //--- element will be "visible" only if the group is "visible" and the element is completely "within" this group
      control.Visible(Contains(control));
     }
   else
      control.Hide();
//--- "projecting" the group flag "enabled" to the added element
   if(IS_ENABLED)
      control.Enable();
   else
      control.Disable();
//--- adding
   return(m_controls.Add(control));
  }

A definição da visibilidade do objeto é criada SÓ QUANDO O CONTROLE É ADICIONADO à área do cliente. Esta é uma desvantagem que pode aparecer depois após maximizar e minimizar o painel.

Exemplo: ao definir ambos os parâmetros de entrada como "false" e, depois, minimizar e maximizar o painel. Como resultado, após criar o painel, é criado o botão "Caption", embora esteja visualmente oculto (já que o botão está fora da área de cliente, isto é, ele fica oculto quando adicionado à área do cliente). Porém, depois que o painel é minimizado e maximizado, já não se verifica a visibilidade dos elementos adicionados e, portanto, o botão Caption fica visível:

CMyWndClient button caption

Neste arquivo, está a classe herdada da classe CWndClient — área de cliente. Este arquivo contém todas as funcionalidades:

  • para criação de vossa área de cliente, 
  • para criação e adição de controles, 
  • para processamento do evento para maximizar o painel,
  • para processamento de eventos de clique na barra de rolagem horizontal,
  • para processamento de eventos de clique em botões — alteração da cor do plano de fundo da área de cliente e da cor da barra de título.

Rolagem horizontal e controles ocultos

Como a classe do painel é herdada da classe CWndClient e a classe CWndClient é tanto o controle combinado "Área de cliente" quanto a classe base para criar as áreas com barras de rolagem, ative em vossa área de cliente uma barra de rolagem horizontal:

//+------------------------------------------------------------------+
//| Criando o painel                                                  |
//+------------------------------------------------------------------+
bool CMyWndClient::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2)
  {
//---
   if(!CWndClient::Create(chart,name,subwin,x1,y1,x2,y2))
      return(false);
//--- enable horizontal scrollbar
   if(!HScrolled(true))
      return(false);
   m_scroll_h.MaxPos(5);
   m_scroll_h.CurrPos(5);
   Print("CurrPos: ",m_scroll_h.CurrPos());
   if(!AddButton1())

Para poder mover a barra de rolagem, defina seu número de gradações: valor máximo de posição, e imediatamente posicione a barra de rolagem na posição mais à direita

A rolagem horizontal também é usada para capturar o evento para maximizar o painel, para fazer isso, capture o evento ON_SHOW para o objeto m_scroll_h e chame o manipulador OnShowScrollH:

//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CMyWndClient)
ON_EVENT(ON_CLICK,m_button1,OnClickButton1)
ON_EVENT(ON_CLICK,m_button2,OnClickButton2)
ON_EVENT(ON_SHOW,m_scroll_h,OnShowScrollH)
EVENT_MAP_END(CWndClient)

EVENT_MAP_BEGIN é apenas o método OnEvent escrito por macros do bloco "Events" e do bloco "Macro of event handling map" do arquivo "Defines.mqh" (para detalhes, veja o artigo Como criar um painel gráfico de qualquer complexidade).

No manipulador OnShowScrollH , verifique o valor do sinalizador interno m_hide_invisble (este sinalizador aceita o valor da variável de entrada "ocultar o invisível" do arquivo MyWndClient.mq5 através do método CMyWndClient::HideInvisble), se você não precisa esconder os elementos, apenas saia do procedimento:

//+------------------------------------------------------------------+
//| Você já tem uma barra de rolagem, oculte/exiba botões                       |
//+------------------------------------------------------------------+
void CMyWndClient::OnShowScrollH(void)
  {
   if(!m_hide_invisble)
      return;
   int total=CWndClient::ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=Control(i);
      string name=obj.Name();
      //---
      if(StringFind(name,"Button")!=-1)
        {
         CButton *button=(CButton*)obj;
         button.Visible(Contains(GetPointer(button)));
         ChartRedraw();
        }
     }
  }

Se você quiser esconder elementos invisíveis, num ciclo em todos os objetos da área de cliente, procure os objetos contendo em seu nome "Button" e forçosamente verifique/atribua visibilidade.

Cliques da barra de rolagem horizontal

Para cliques na barra de rolagem horizontal, é preciso permitir o movimento de dois botões adicionados em vossa área de cliente. Para fazer isso, substitua os manipuladores OnScrollLineRightOnScrollLineLeft - manipuladores para clicar nos botões da rolagem horizontal. Se houver um clique no botão direito da rolagem horizontal, mova os botões (método ShiftButton um passo m_scroll_size. Se houver um clique no botão esquerdo, mova os botões um passo "-m_scroll_size", isto é, defina o deslocamento negativo:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
bool CMyWndClient::OnScrollLineRight(void)
  {
   Print(__FUNCTION__);
   ShiftButton(GetPointer(m_button1),m_scroll_size);
   ShiftButton(GetPointer(m_button2),m_scroll_size);
   return(true);
  }
//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
bool CMyWndClient::OnScrollLineLeft(void)
  {
   Print(__FUNCTION__);
   ShiftButton(GetPointer(m_button1),-m_scroll_size);
   ShiftButton(GetPointer(m_button2),-m_scroll_size);
   return(true);
  }
No método CMyWndClient::ShiftButton, obtenha o objeto com as coordenadas do botão, defina o deslocamento de coordenadas, mova o botão e forçosamente verifique/defina a visibilidade do botão:

//+------------------------------------------------------------------+
//| Deslocamento do botão para a esquerda ou para a direita (dependendo de shift)           |
//+------------------------------------------------------------------+
bool CMyWndClient::ShiftButton(CButton *button,const int shift)
  {
   Print(__FUNCTION__);
//--- mova o botão
   CRect rect=button.Rect();
   rect.Move(rect.left+shift,rect.top);
   button.Move(rect.left,rect.top);
   button.Visible(Contains(GetPointer(button)));
   return(true);
  }

Veja como fica (não se esqueça de definir o parâmetro "ocultar o invisível" como true):

CMyWndClient move buttons



Novos projetos: como eles podem ajudar no estudo de painéis?

Para escrever algo, você sempre tem que estudar código. No caso de criar painéis, o estudo de classes pode consumir muito tempo. Isto se deve principalmente ao fato de que não há representação visual da estrutura de classes. Além disso, é muito difícil entender quais classes da biblioteca padrão estão envolvidas na criação de painéis. 

Felizmente, não há muito tempo foram disponibilizados Novos Projetos no editor MetaEditor.

O projeto é um arquivo separado com a extensão "MQPROJ", que armazena as configurações do programa, parâmetros de compilação e informações sobre todos os arquivos usados. Para trabalhar facilmente com o projeto, existe uma guia separada no Navegador. Ela mostra todos os arquivos usados por categorias: incluídos, de recurso, de cabeçalhos, etc.

Veja a descrição da guia criada separadamente: "Ela mostra por categoria todos os arquivos usados: de inclusão, de recursos, de cabeçalho, etc."! Isso é exatamente o que precisamos!

Vamos tentar criar um projeto a partir do último arquivo "Live panel and button Clicks.mq5". Para fazer isso, clicamos com o botão direito no arquivo "Live panel and button Clicks.mq5" e no menu flutuante, selecionamos a opção "Novo projeto a partir de arquivo de origem":

New Project from Source

Como resultado, será criado um novo projeto e, na janela "Navegador", será aberta a guia "Projeto" onde você poderá ver todos os arquivos usados:

New Project

"Wnd.mqh" (que contém a classe CWnd), "Dialog.mqh (contendo as classes CDialog e CAppDialog), Button.mhq (classe CButton). Esta guia facilita ir para a classe requerida. Assim, é muito mais conveniente navegar pelas guias do editor MetaEditor. Por exemplo, eu tenho um pequeno "zoológico" de vários arquivos. Ir a partir dele, por exemplo, para Dialog.mqh através da pesquisa nas guias é problemático:

Many windows

Fim do artigo

No artigo, é apresentada uma maneira bastante incomum de acessar as propriedades "Cor do plano de fundo" e "Cor do quadro", para os controles do painel. Pessoalmente, nunca vi um método assim. Todo o secreto está em saber que todos os elementos do painel são herdados da classe pai CWnd, logo, o objeto - a classe do painel que está sendo criado - é um contêiner para todos os controles. Assim, você pode percorrer todos os controles e obter/definir as propriedades desejadas.

Nome do arquivo Comentário
 Live panel.mq5  Painel sem adição de controles. Ao ser arrastado, torna-se transparente
 Live panel and Button.mq5  Painel com adição de botão. Ao ser arrastado, o painel se torna transparente, enquanto o botão permanece da mesma cor
 Live panel and transparent Button.mq5  Painel com adição de botão. Ao ser arrastado, o painel e o botão se tornam transparentes
 Live panel and button Clicks.mq5  Painel com dois botões. Processamento de cliques nos botões: geração de uma cor de fundo para o painel e para a barra de título
 MyAppWindow.mq5 e MyAppDialog.mqh  Exemplo de criação de painel por herança da CAppDialog
 MyWndClient.mq5 e MyWndClient.mqh  Exemplo de criação de um painel herdando da CWndClient — da classe da área de cliente

Traduzido do russo pela MetaQuotes Software Corp.
Artigo original: https://www.mql5.com/ru/articles/4575

Arquivos anexados |
MQL5.zip (18.86 KB)
Visualização dos resultados de otimização pelo critério selecionado Visualização dos resultados de otimização pelo critério selecionado

No artigo, continuamos a desenvolver o aplicativo MQL para trabalhar com resultados de otimização que foi iniciado em artigos anteriores. Desta vez, veremos um exemplo em que podemos gerar uma tabela de melhores resultados após a otimização de parâmetros, especificando através da interface gráfica outro critério.

Construímos um ZigZag de osciladores. Exemplo de execução do trabalho segundo os termos de referência Construímos um ZigZag de osciladores. Exemplo de execução do trabalho segundo os termos de referência

O artigo apresenta a criação do indicador ZigZag de acordo com os termos de referência para um dos exercícios descritos no artigo "Como criar uma especificação de requisitos para solicitar um indicador". O indicador é construído com base em extremos determinados com a ajuda de um oscilador. O indicador suporta o uso de um dos seguintes osciladores: WPR, CCI, Chaikin, RSI, Stochastic Oscillator.

Como transferir a parte de cálculo de qualquer indicador para o código do EA Como transferir a parte de cálculo de qualquer indicador para o código do EA

Existem vários motivos que justificam a transferência do código do indicador para o EA. Mas como avaliar os prós e contras desta abordagem? Este artigo propõe uma maneira de transferir o código do indicador para um EA. Além disso, são realizados vários experimentos para avaliar a velocidade de funcionamento do EA.

Como analisar os trades do Sinal selecionado no gráfico Como analisar os trades do Sinal selecionado no gráfico

O serviço Sinais de negociação se desenvolve rapidamente. E como você está confiando seu dinheiro a um provedor do sinais, seria bom minimizar o risco de perder o depósito. Como lidar com essa selva de sinais de negociação? Como encontrar esse sinal que trará o lucro para você? O artigo propõe a criação de uma ferramenta para analisar visualmente o histórico de trades de sinais de negociação no gráfico do instrumento.