English Русский 中文 Español Deutsch 日本語
Interfaces gráficas XI: Caixas de Edição de Texto e Caixas de Combinação nas células da tabela (build 15)

Interfaces gráficas XI: Caixas de Edição de Texto e Caixas de Combinação nas células da tabela (build 15)

MetaTrader 5Exemplos | 9 outubro 2017, 09:17
1 075 0
Anatoli Kazharski
Anatoli Kazharski

Conteúdo

Introdução

O primeiro artigo Interfaces gráficas I: Preparação da Estrutura da Biblioteca (Capítulo 1) considera em detalhes a finalidade desta biblioteca. A versão completa da biblioteca no estágio atual de desenvolvimento está disponível no final de cada artigo da série. Os arquivos devem estar localizados nas mesmas pastas que o arquivo baixado.

A próxima atualização dará enfoque ao controle da tabela (a classe CTable). Anteriormente tornou-se possível adicionar as caixas de seleção e botões nas células da tabela. Vamos expandir a gama desses controles com caixas de texto e caixas de combinação. A nova versão também adiciona a capacidade de gerenciar os tamanhos das janelas em tempo de execução do aplicativo.

Redimensionamento da janela

Para facilitar o uso das listas, tabelas ou caixas de texto multilinha, muitas vezes é necessário reduzir a janela ou maximizá-la para todo o gráfico. Existem várias maneiras de gerenciar o tamanho da janela.

  • O modo para alternar rapidamente da tela normal para a tela cheia e voltar com um único clique em um botão especial.
  • Clicar duas vezes no título da janela também maximiza a janela para a tela cheia. Ao clicar duas vezes novamente, a janela retorna para o estado anterior.
  • A janela pode ser redimensionada arrastando suas bordas com o botão esquerdo do mouse.

Vamos ver como isso é implementado na biblioteca. 

Uma instância separada da classe CButton foi declarada para criar o botão de modo tela cheia. O método público CButton::GetFullscreenButtonPointer() é projetado para obter o ponteiro para o botão. Este botão está desativado no formulário por padrão. Use o método CButton::FullscreenButtonIsUsed() para habilitar o botão. 

//+------------------------------------------------------------------+
//| A Classe Formulário para os Controles                            |
//+------------------------------------------------------------------+
class CWindow : public CElement
  {
private:
   //--- Objetos para a criação de um formulário
   CButton           m_button_fullscreen;
   //--- Presença do botão para maximizar a janela no modo tela cheia
   bool              m_fullscreen_button;
   //---
public:
   //--- Retorna os ponteiros para os botões do formulário
   CButton          *GetFullscreenButtonPointer(void)                { return(::GetPointer(m_button_fullscreen)); }
   //--- Usa o botão de tela cheia
   void              FullscreenButtonIsUsed(const bool state)        { m_fullscreen_button=state;                 }
   bool              FullscreenButtonIsUsed(void)              const { return(m_fullscreen_button);               }
  };

O botão de tela cheia é criado no método geral CWindow::CreateButtons() (veja o código abaixo), onde todos os botões habilitados do formulário são criados. Semelhante aos botões para exibir dicas contexto e minimizar o formulário, o botão do modo tela inteira só pode ser usado na janela principal. 

//+------------------------------------------------------------------+
//| Cria botões no formulário                                        |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Controls\\full_screen.bmp"
#resource "\\Images\\EasyAndFastGUI\\Controls\\minimize_to_window.bmp"
//---
bool CWindow::CreateButtons(void)
  {
//--- Se o tipo do programa for um script, retorna
   if(CElementBase::ProgramType()==PROGRAM_SCRIPT)
      return(true);
//--- Contador, tamanho, número
   int i=0,x_size=20;
   int buttons_total=4;
//--- O caminho para o arquivo
   string icon_file="";
//--- Exceção na área de captura
   m_right_limit=0;
//---
   CButton *button_obj=NULL;
//---
   for(int b=0; b<buttons_total; b++)
     {
      ...
      else if(b==1)
        {
         m_button_fullscreen.MainPointer(this);
         //--- Retorna, se (1) o botão não estiver habilitado ou (2) for uma caixa de diálogo
         if(!m_fullscreen_button || m_window_type==W_DIALOG)
            continue;
         //---
         button_obj=::GetPointer(m_button_fullscreen);
         icon_file="Images\\EasyAndFastGUI\\Controls\\full_screen.bmp";
        }
      ...
     }
//---
   return(true);
  }

O tamanho mínimo da janela é definido automaticamente para o tamanho especificado ao criar o controle. Mas estes valores podem ser substituídos. Na versão atual, não é possível definir o tamanho da janela para menos de 200x200 pixels. Isso é controlado no método de inicialização das propriedades do controle — CWindow::InitializeProperties(). 

class CWindow : public CElement
  {
private:
   //--- Tamanho mínimo da janela
   int               m_minimum_x_size;
   int               m_minimum_y_size;
   //---
public:
   /--- Definindo o tamanho mínimo da janela
   void              MinimumXSize(const int x_size)                  { m_minimum_x_size=x_size;                   }
   void              MinimumYSize(const int y_size)                  { m_minimum_y_size=y_size;                   }
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CWindow::CWindow(void) : m_minimum_x_size(0),
                         m_minimum_y_size(0)
  {
...
  }
//+------------------------------------------------------------------+
//| Inicialização das propriedades                                   |
//+------------------------------------------------------------------+
void CWindow::InitializeProperties(const long chart_id,const int subwin,const string caption_text,const int x_gap,const int y_gap)
  {
...
   m_x_size         =(m_x_size<1)? 200 : m_x_size;
   m_y_size         =(m_y_size<1)? 200 : m_y_size;
...
   m_minimum_x_size =(m_minimum_x_size<200)? m_x_size : m_minimum_x_size;
   m_minimum_y_size =(m_minimum_y_size<200)? m_y_size : m_minimum_y_size;
...
  }

Antes de redimensionar a janela durante uma maximização para a tela cheia, é necessário armazenar as dimensões atuais, as coordenadas e os modos de redimensionamento automático, caso tenham sido configurados. Esses valores são armazenados em campos privados especiais da classe:

class CWindow : public CElement
  {
private:
   //--- Últimas coordenadas e dimensões da janela antes de alternar para a tela cheia
   int               m_last_x;
   int               m_last_y;
   int               m_last_x_size;
   int               m_last_y_size;
   bool              m_last_auto_xresize;
   bool              m_last_auto_yresize;
  };

O método CWindow::OnClickFullScreenButton() é usado para manipular o clique no botão de tela cheia. Ele verifica primeiro o identificador e o índice do controle e, em seguida, o código é dividido em dois blocos:

  • Se a janela não for maximizada no momento, ela é alternada para o modo tela cheia. Em seguida, as dimensões do gráfico atual são obtidas, os tamanhos atuais, as coordenadas da janela e os modos de redimensionamento automático são armazenados nos campos especiais da classe. Uma vez que o modo de tela cheia exige que o formulário seja redimensionado automaticamente quando o tamanho do gráfico principal se altera, é necessário habilitar os modos de redimensionamento automático. Após isso, os tamanhos das janelas estão configurados. Ao mesmo tempo, a localização da janela é colocada no canto superior esquerdo, de modo que ela preenche todo o espaço do gráfico. O ícone no botão é substituído por outro.
  • Se a janela estiver maximizada no momento, altere-a para o tamanho da janela anterior. Aqui, os modos de alternância automática da janela são convertidos para o estado anterior. Então, dependendo de qual dos modos está desabilitado, é definido o tamanho da janela anterior. Além disso, a localização anterior e o ícone correspondente ao botão são definidos.

No final do método CWindow::OnClickFullScreenButton(), o formulário é redesenhado:

class CWindow : public CElement
  {
private:
   //--- Estado da janela no modo de tela cheia
   bool              m_is_fullscreen;
   //---
public:
   //--- Muda para a tela cheia ou para o tamanho da janela anterior
   bool              OnClickFullScreenButton(const int id=WRONG_VALUE,const int index=WRONG_VALUE);
  };
//+------------------------------------------------------------------+
//| Muda para a tela cheia ou para o tamanho do formulário anterior  |
//+------------------------------------------------------------------+
bool CWindow::OnClickFullScreenButton(const int id=WRONG_VALUE,const int index=WRONG_VALUE)
  {
//--- Verifica o identificador e o índice de controle se houver uma chamada externa
   int check_id    =(id!=WRONG_VALUE)? id : CElementBase::Id();
   int check_index =(index!=WRONG_VALUE)? index : CElementBase::Index();
//--- Retorna, se os índices não coincidem
   if(check_id!=m_button_fullscreen.Id() || check_index!=m_button_fullscreen.Index())
      return(false);
//--- Se a janela não estiver em tela cheia
   if(!m_is_fullscreen)
     {
      //--- Muda para tela cheia
      m_is_fullscreen=true;
      //--- Obtém as dimensões atuais da janela do gráfico
      SetWindowProperties();
      //--- Armazena as coordenadas e as dimensões atuais do formulário
      m_last_x            =m_x;
      m_last_y            =m_y;
      m_last_x_size       =m_x_size;
      m_last_y_size       =m_full_height;
      m_last_auto_xresize =m_auto_xresize_mode;
      m_last_auto_yresize =m_auto_yresize_mode;
      //--- Habilita o redimensionamento automático do formulário
      m_auto_xresize_mode=true;
      m_auto_yresize_mode=true;
      //--- Maximiza o formulário para todo o gráfico
      ChangeWindowWidth(m_chart.WidthInPixels()-2);
      ChangeWindowHeight(m_chart.HeightInPixels(m_subwin)-3);
      //--- Atualiza a localização
      m_x=m_y=1;
      Moving(m_x,m_y);
      //--- Substitui o ícone do botão
      m_button_fullscreen.IconFile("Images\\EasyAndFastGUI\\Controls\\minimize_to_window.bmp");
      m_button_fullscreen.IconFileLocked("Images\\EasyAndFastGUI\\Controls\\minimize_to_window.bmp");
     }
//--- Se a janela estiver em tela cheia
   else
     {
      //--- Muda para o tamanho da janela anterior
      m_is_fullscreen=false;
      //--- Desativa o redimensionamento automático
      m_auto_xresize_mode=m_last_auto_xresize;
      m_auto_yresize_mode=m_last_auto_yresize;
      //--- Se o modo estiver desativado, define o tamanho anterior
      if(!m_auto_xresize_mode)
         ChangeWindowWidth(m_last_x_size);
      if(!m_auto_yresize_mode)
         ChangeWindowHeight(m_last_y_size);
      //--- Atualiza a localização
      m_x=m_last_x;
      m_y=m_last_y;
      Moving(m_x,m_y);
      //--- Substitui o ícone do botão
      m_button_fullscreen.IconFile("Images\\EasyAndFastGUI\\Controls\\full_screen.bmp");
      m_button_fullscreen.IconFileLocked("Images\\EasyAndFastGUI\\Controls\\full_screen.bmp");
     }
//--- Remove o foco do botão
   m_button_fullscreen.MouseFocus(false);
   m_button_fullscreen.Update(true);
   return(true);
  }

O método CWindow::OnClickFullScreenButton() é chamado no manipulador de eventos do controle quando o evento personalizado ON_CLICK_BUTTON chega. 

//+------------------------------------------------------------------+
//| Manipulador de eventos do gráfico                                |
//+------------------------------------------------------------------+
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Manipulação do evento do clique nos botões do formulário
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      ...
      //--- Verifica o modo tela cheia
      if(OnClickFullScreenButton((uint)lparam,(uint)dparam))
         return;
      ...
      //---
      return;
     }
  }

O resultado será o seguinte:

 Fig. 1. Demonstração de alternância entre a tela cheia e a anterior.

Fig. 1. Demonstração de alternância entre a tela cheia e a anterior.

Para alternar o modo tela cheia e voltar através do duplo clique no título da janela, agora é suficiente processar o evento de duplo clique (ON_DOUBLE_CLICK) sobre o título da janela no manipulador de eventos do controle.

void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Manipulação do evento de clique duplo em um objeto
   if(id==CHARTEVENT_CUSTOM+ON_DOUBLE_CLICK)
     {
      //--- Se o evento foi gerado no título da janela
      if(CursorInsideCaption(m_mouse.X(),m_mouse.Y()))
         OnClickFullScreenButton(m_button_fullscreen.Id(),m_button_fullscreen.Index());
      //---
      return;
     }
  }

É assim que ele funciona:

 Fig. 2. Demonstração de alternância para a tela cheia através do clique duplo no título.

Fig. 2. Demonstração de alternância para a tela cheia através do clique duplo no título.

Agora, considere o modo de redimensionamento da janela através do arraste de suas bordas. Use o método CWindow::ResizeMode() para habilitá-lo.

class CWindow : public CElement
  {
private:
   //--- Modo de redimensionamento da janela
   bool              m_xy_resize_mode;
   //---
public:
   //--- Capacidade de redimensionamento da janela
   bool              ResizeMode(void)                          const { return(m_xy_resize_mode);                  }
   void              ResizeMode(const bool state)                    { m_xy_resize_mode=state;                    }
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CWindow::CWindow(void) : m_xy_resize_mode(false)
  {
...
  }

Para monitorar o botão esquerdo do mouse, clique nas bordas da janela, mais um identificador (PRESSED_INSIDE_BORDER) é exigido na enumeração ENUM_MOUSE_STATE, que está localizada no arquivo Enums.mqh.

//+------------------------------------------------------------------+
//|                                                        Enums.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Enumeração das áreas de pressionamento do botão esquerdo do mouse|
//+------------------------------------------------------------------+
enum ENUM_MOUSE_STATE
  {
   NOT_PRESSED           =0,
   PRESSED_INSIDE        =1,
   PRESSED_OUTSIDE       =2,
   PRESSED_INSIDE_HEADER =3,
   PRESSED_INSIDE_BORDER =4
  };

 Se o modo de redimensionamento estiver ativado, um objeto gráfico com o novo identificador MP_WINDOW_RESIZE da enumeração ENUM_MOUSE_POINTER é criada para o cursor do mouse.

//+------------------------------------------------------------------+
//| Enumeração dos tipos de ponteiros                                |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_POINTER
  {
   MP_CUSTOM            =0,
   MP_X_RESIZE          =1,
   MP_Y_RESIZE          =2,
   MP_XY1_RESIZE        =3,
   MP_XY2_RESIZE        =4,
   MP_WINDOW_RESIZE     =5,
   MP_X_RESIZE_RELATIVE =6,
   MP_Y_RESIZE_RELATIVE =7,
   MP_X_SCROLL          =8,
   MP_Y_SCROLL          =9,
   MP_TEXT_SELECT       =10
  };

O método CreateResizePointer() foi adicionado à classe CWindow para criar o objeto gráfico para o cursor do mouse:

class CWindow : public CElement
  {
private:
   bool              CreateResizePointer(void);
  };
//+------------------------------------------------------------------+
//| Cria o cursor do mouse para redimensionar                        |
//+------------------------------------------------------------------+
bool CWindow::CreateResizePointer(void)
  {
//--- Retorna, se o modo de redimensionamento estiver desativado
   if(!m_xy_resize_mode)
      return(true);
//--- Propriedades
   m_xy_resize.XGap(13);
   m_xy_resize.YGap(11);
   m_xy_resize.XSize(23);
   m_xy_resize.YSize(23);
   m_xy_resize.Id(CElementBase::Id());
   m_xy_resize.Type(MP_WINDOW_RESIZE);
//--- Cria o controle
   if(!m_xy_resize.CreatePointer(m_chart_id,m_subwin))
      return(false);
//---
   return(true);
  }

Foi necessário implementar vários métodos para redimensionar a janela. Vamos considerá-los em ordem.

A localização do cursor do mouse deve ser rastreada quando aparecer na área da janela. Nesta versão, a janela pode ser redimensionada arrastando sua borda esquerda, direita ou de baixo. O método CWindow::ResizeModeIndex() monitora o foco sobre uma das bordas listadas e armazena o índice da borda para posterior manipulação em outros métodos. As coordenadas do cursor do mouse relativas à janela são passadas para este método para a realização de cálculos.

class CWindow : public CElement
  {
private:
   //--- Índice da borda para redimensionar a janela
   int               m_resize_mode_index;
   //---
private:
   //--- Retorna o índice do modo de redimensionamento da janela
   int               ResizeModeIndex(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Retorna o índice do modo de redimensionamento da janela          |
//+------------------------------------------------------------------+
int CWindow::ResizeModeIndex(const int x,const int y)
  {
//--- Retorna o índice da borda, se já estiver sendo arrastada
   if(m_resize_mode_index!=WRONG_VALUE && m_mouse.LeftButtonState())
      return(m_resize_mode_index);
//--- Largura, deslocamento e índice da borda
   int width  =5;
   int offset =15;
   int index  =WRONG_VALUE;
//--- Verifica o foco na borda esquerda
   if(x>0 && x<width && y>m_caption_height+offset && y<m_y_size-offset)
      index=0;
//--- Verifica o foco na borda direita
   else if(x>m_x_size-width && x<m_x_size && y>m_caption_height+offset && y<m_y_size-offset)
      index=1;
//--- Verifica o foco na borda inferior
   else if(y>m_y_size-width && y<m_y_size && x>offset && x<m_x_size-offset)
      index=2;
//--- Se o índice for obtido, marca a área do clique
   if(index!=WRONG_VALUE)
      m_clamping_area_mouse=PRESSED_INSIDE_BORDER;
//--- Retorna o índice de área
   return(index);
  }

Campos de classe auxiliares serão necessários: para determinar os pontos de captura, armazenar as dimensões iniciais e para os cálculos subsequentes. Uma vez que o processo de redimensionamento for iniciado, será necessário gerar uma mensagem para formar a lista de controles disponíveis. Portanto, será necessário também de um método para gerar a mensagem para restaurar os controles e redefinir os campos de serviço: CWindow::ZeroResizeVariables().

class CWindow : public CElement
  {
private:
   //--- Variáveis ​​relacionadas ao redimensionamento da janela
   int               m_x_fixed;
   int               m_size_fixed;
   int               m_point_fixed;
   //---
private:
   //--- Zerando as variáveis
   void              ZeroResizeVariables(void);
  };
//+------------------------------------------------------------------+
//| Reseta as variáveis ​​relacionadas ao redimensionamento da janela  |
//+------------------------------------------------------------------+
void CWindow::ZeroResizeVariables(void)
  {
//--- Retorna, se já tiver zerado
   if(m_point_fixed<1)
      return;
//--- Zero
   m_x_fixed     =0;
   m_size_fixed  =0;
   m_point_fixed =0;
//--- Envia uma mensagem para restaurar os controles disponíveis
   ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),1,"");
//--- Envia uma mensagem sobre a alteração na interface gráfica
   ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,"");
  }

O método CWindow::CheckResizePointer() foi implementado para determinar se os modos de exibição e ocultação do cursor do mouse estão prontos para redimensionar a janela. Aqui o método CWindow::ResizeModeIndex() é usado para determinar o índice da borda. 

Se o cursor do mouse ainda não for exibido, então, em um determinado índice da borda, é necessário definir o ícone correspondente, ajustar a posição e a saída do ponteiro.

Caso a determinação do índice da borda mostrar que o cursor do mouse já está em exibição, ele será movido após o cursor do mouse, se houver foco em uma das fronteiras. Se não houver foco e o botão esquerdo do mouse for liberado, o cursor está oculto e as variáveis ​​são zeradas.

O método CWindow::CheckResizePointer() retorna true se a borda da janela para redimensionamento for definida e false caso contrário.

class CWindow : public CElement
  {
private:
   //--- Verifique a prontidão para redimensionar a janela
   bool              CheckResizePointer(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Verifica a prontidão para redimensionar a janela                 |
//+------------------------------------------------------------------+
bool CWindow::CheckResizePointer(const int x,const int y)
  {
//--- Determina o índice da borda atual
   m_resize_mode_index=ResizeModeIndex(x,y);
//--- Se o cursor estiver oculto
   if(!m_xy_resize.IsVisible())
     {
      //--- Se a borda for definida
      if(m_resize_mode_index!=WRONG_VALUE)
        {
         //--- Para determinar o índice do ícone exibido do ponteiro do mouse
         int index=WRONG_VALUE;
         //--- Se em bordas verticais
         if(m_resize_mode_index==0 || m_resize_mode_index==1)
            index=0;
         //--- Se em bordas horizontais
         else if(m_resize_mode_index==2)
            index=1;
         //--- Altera o ícone
         m_xy_resize.ChangeImage(0,index);
         //--- Move, redesenhar e exibe
         m_xy_resize.Moving(m_mouse.X(),m_mouse.Y());
         m_xy_resize.Update(true);
         m_xy_resize.Reset();
         return(true);
        }
     }
   else
     {
      //--- Move o ponteiro
      if(m_resize_mode_index!=WRONG_VALUE)
         m_xy_resize.Moving(m_mouse.X(),m_mouse.Y());
      //--- Oculta o cursor
      else if(!m_mouse.LeftButtonState())
        {
         //--- Oculta o ponteiro e redefine as variáveis
         m_xy_resize.Hide();
         ZeroResizeVariables();
        }
      //--- Atualiza o gráfico
      m_chart.Redraw();
      return(true);
     }
//---
   return(false);
  }

O método CWindow::CheckDragWindowBorder() é usado para verificar o início do arraste da borda da janela. No momento em que a borda é arrastada, é necessário armazenar as dimensões atuais e a coordenada do ponto inicial de arraste nos campos da classe. Ao mesmo tempo, uma mensagem para determinar os controles disponíveis é enviado

Se as chamadas subsequentes para este método mostrarem que a borda já está sendo arrastada, então é necessário calcular a distância passada neste modo e retornar o valor resultante.

class CWindow : public CElement
  {
private:
   //--- Verifica o arraste da borda da janela
   int               CheckDragWindowBorder(const int x,const int y);
  };
//+------------------------------------------------------------------+
/ | Verifica o arrastae da borda da janela                           |
//+------------------------------------------------------------------+
int CWindow::CheckDragWindowBorder(const int x,const int y)
  {
//--- Para determinar a distância do deslocamento
   int distance=0;
//--- Se a borda não for arrastada
   if(m_point_fixed<1)
     {
      //--- Se redimensionar ao longo do eixo X
      if(m_resize_mode_index==0 || m_resize_mode_index==1)
        {
         m_x_fixed     =m_x;
         m_size_fixed  =m_x_size;
         m_point_fixed =x;
        }
      //--- se redimensionar ao longo do eixo Y
      else if(m_resize_mode_index==2)
        {
         m_size_fixed  =m_y_size;
         m_point_fixed =y;
        }
      //--- Envia uma mensagem para determinar os controles disponíveis
      ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),0,"");
      //--- Envia uma mensagem sobre a alteração na interface gráfica
      ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,"");
      return(0);
     }
//--- Se esta é a borda esquerda
   if(m_resize_mode_index==0)
      distance=m_mouse.X()-m_x_fixed;
//--- Se esta é a borda direita
   else if(m_resize_mode_index==1)
      distance=x-m_point_fixed;
//--- Se esta é a borda inferior
   else if(m_resize_mode_index==2)
      distance=y-m_point_fixed;
//--- Retorna a distância passada
   return(distance);
  }

O resultado retornado pelo método CWindow::CheckDragWindowBorder() é passado para o método CWindow::CalculateAndResizeWindow(), onde as coordenadas e dimensões da janela são calculadas em relação à sua borda

class CWindow : public CElement
  {
private:
   //--- Cálculo e redimensionamento da janela
   void              CalculateAndResizeWindow(const int distance);
  };
//+------------------------------------------------------------------+
//| Cálcula e redimensiona a janela                                  |
//+------------------------------------------------------------------+
void CWindow::CalculateAndResizeWindow(const int distance)
  {
//--- Borda esquerda
   if(m_resize_mode_index==0)
     {
      int new_x      =m_x_fixed+distance-m_point_fixed;
      int new_x_size =m_size_fixed-distance+m_point_fixed;
      //--- Retorna, se exceder os limites
      if(new_x<1 || new_x_size<=m_minimum_x_size)
         return;
      //--- Coordenadas
      CElementBase::X(new_x);
      m_canvas.X_Distance(new_x);
      //--- Define e armazena o tamanho
      CElementBase::XSize(new_x_size);
      m_canvas.XSize(new_x_size);
      m_canvas.Resize(new_x_size,m_canvas.YSize());
     }
//--- Borda direita
   else if(m_resize_mode_index==1)
     {
      int gap_x2     =m_chart_width-m_mouse.X()-(m_size_fixed-m_point_fixed);
      int new_x_size =m_size_fixed+distance;
      //--- Retorna, se exceder os limites
      if(gap_x2<1 || new_x_size<=m_minimum_x_size)
         return;
      //--- Define e armazena o tamanho
      CElementBase::XSize(new_x_size);
      m_canvas.XSize(new_x_size);
      m_canvas.Resize(new_x_size,m_canvas.YSize());
     }
//--- Borda inferior
   else if(m_resize_mode_index==2)
     {
      int gap_y2=m_chart_height-m_mouse.Y()-(m_size_fixed-m_point_fixed);
      int new_y_size=m_size_fixed+distance;
      //--- Retorna, se exceder os limites
      if(gap_y2<2 || new_y_size<=m_minimum_y_size)
         return;
      //--- Define e armazena o tamanho
      m_full_height=new_y_size;
      CElementBase::YSize(new_y_size);
      m_canvas.YSize(new_y_size);
      m_canvas.Resize(m_canvas.XSize(),new_y_size);
     }
  }


Os métodos CWindow::CheckDragWindowBorder() e CWindow::CheckDragWindowBorder() são chamados dentro do método CWindow::UpdateSize(). Aqui, no início do método, há uma verificação se o botão esquerdo do mouse for pressionado. Se o botão for liberado, todos os valores da variável relacionados ao redimensionamento da janela são reiniciados e o programa deixa o método.

Se o botão esquerdo do mouse for pressionado, então (1) é determinado a distância passada pela borda no estado de arraste, (2) calculado e redimensionado a janela, (3) redesenhado a janela e (4) ajustado a localização dos seus elementos.

No final do método, dependendo do eixo que a janela foi redimensionada, um evento é gerado, que será usado mais tarde para redimensionar todos os controles anexados à janela e que tenham o modo correspondente habilitado.

class CWindow : public CElement
  {
private:
   //--- Atualiza os tamanhos das janelas
   void              UpdateSize(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Atualiza os tamanhos das janelas                                 |
//+------------------------------------------------------------------+
void CWindow::UpdateSize(const int x,const int y)
  {
//--- Se foi finalizado e o botão esquerdo do mouse foi liberado, redefine os valores
   if(!m_mouse.LeftButtonState())
     {
      ZeroResizeVariables();
      return;
     }
//--- Retorna, se a captura e o movimento da borda ainda não forem iniciadas
   int distance=0;
   if((distance=CheckDragWindowBorder(x,y))==0)
      return;
//--- Cálculo e redimensionamento da janela
   CalculateAndResizeWindow(distance);
//--- Redesenha a janela
   Update(true);
//--- Atualiza a posição dos objetos
   Moving(m_x,m_y);
//--- Uma mensagem de que os tamanhos da janela foram alterados
   if(m_resize_mode_index==2)
      ::EventChartCustom(m_chart_id,ON_WINDOW_CHANGE_YSIZE,(long)CElementBase::Id(),0,"");
   else
      ::EventChartCustom(m_chart_id,ON_WINDOW_CHANGE_XSIZE,(long)CElementBase::Id(),0,"");
  }

Todos os métodos listados para medir as dimensões da janela são chamados no método principal CWindow::ResizeWindow(). Primeiro, verifica se a janela está disponível. Então, se o botão esquerdo do mouse não foi pressionado sobre uma das bordas da janela, o programa deixa o método. Então, seguem mais três verificações: (1) se o modo de redimensionamento está ativado, (2) se a janela está maximizada na tela cheia e (3) não está minimizada.

Se todas as verificações forem passadas, as coordenadas relativas ao cursor do mouse são obtidas, e se a borda da janela foi capturada, o controle é redimensionado.

class CWindow : public CElement
  {
private:
   //--- Controla dos tamanhos das janelas
   void              ResizeWindow(void);
  };
//+------------------------------------------------------------------+
//| Controla dos tamanhos das janelas                                |
//+------------------------------------------------------------------+
void CWindow::ResizeWindow(void)
  {
//--- Retorna, se a janela não estiver disponível
   if(!IsAvailable())
      return;
//--- Retorna, se o botão do mouse não foi pressionado sobre o botão do formulário
   if(m_clamping_area_mouse!=PRESSED_INSIDE_BORDER && m_clamping_area_mouse!=NOT_PRESSED)
      return;
//--- Retorna, se (1) o modo de redimensionamento da janela estiver desativado ou 
//    (2) a janela está em tela cheia ou (3) a janela está minimizada
   if(!m_xy_resize_mode || m_is_fullscreen || m_is_minimized)
      return;
//--- Coordenadas
   int x =m_mouse.RelativeX(m_canvas);
   int y =m_mouse.RelativeY(m_canvas);
//--- Verifique a prontidão para alterar a largura das listas
   if(!CheckResizePointer(x,y))
      return;
//--- Atualiza os tamanhos das janelas
   UpdateSize(x,y);
  }

O método CWindow::ResizeWindow() é chamado no manipulador de eventos quando chega um evento para o movimento do cursor do mouse (CHARTEVENT_MOUSE_MOVE). 

É assim que ele funciona:

 Fig. 3. Demonstração do redimensionamento da janela pelo movimento de suas bordas.

Fig. 3. Demonstração do redimensionamento da janela pelo movimento de suas bordas.

Caixas de texto e caixas de combinação nas células da tabela

Se as células da tabela tiverem controles diferentes, a tabela se torna uma ferramenta muito flexível para gerenciar os dados contidos nela. O exemplo mais próximo pode ser visto diretamente na plataforma de negociação MetaTrader, na guia Parâmetros de entrada na janela de configurações aplicativo MQL ou na guia Parâmetros na janela do Testador de Estratégia. As interfaces gráficas com tais capacidades levarão os aplicativos MQL a um novo nível.

 Fig. 4. Janela de configurações de um programa MQL.

Fig. 4. Janela de configurações de um programa MQL.


 Fig. 5. Configurações de uma aplicação MQL no testador de estratégia.

Fig. 5. Configurações de uma aplicação MQL no testador de estratégia.

Um dos artigos anteriores adicionou as caixas de seleção e os botões para as células da tabela. Agora, considere a implementação das caixas de edição de texto e caixas de combinação. 

Primeiramente, dois novos identificadores foram adicionados à enumeração ENUM_TYPE_CELL no arquivo Enums.mqh para denotar os tipos de células da tabela:

  • CELL_COMBOBOX - célula do tipo caixa de combinação.
  • CELL_EDIT - célula do tipo caixa de edição de texto.
//+------------------------------------------------------------------+
//| Enumeração dos tipos de células da tabela                        |
//+------------------------------------------------------------------+
enum ENUM_TYPE_CELL
  {
   CELL_SIMPLE   =0,
   CELL_BUTTON   =1,
   CELL_CHECKBOX =2,
   CELL_COMBOBOX =3,
   CELL_EDIT     =4
  };

Para implementar o planejado, basta criar apenas um controle da caixa de edição de texto (CTextEdit) e/ou um controle da caixa de combinação (CComboBox) na tabela, como componentes do controle CTable. Eles aparecerão ao clicar duas vezes em uma célula, quando o seu valor for modificado. 

Ao definir o tipo de célula usando o método CTable::CellType(), é necessário definir a flag uma vez nos campos especiais da classe, caso o tipo CELL_EDIT ou CELL_COMBOBOX for especificado.

//+------------------------------------------------------------------+
//| Classe para a criação de uma tabela renderizada                  |
//+------------------------------------------------------------------+
class CTable : public CElement
  {
private:
   //--- Presença das células da tabela com caixas de edição de texto e caixas de combinação
   bool              m_edit_state;
   bool              m_combobox_state;
   //---
public:
   //--- Definindo/obtendo o tipo de célula
   void              CellType(const uint column_index,const uint row_index,const ENUM_TYPE_CELL type);
  };
//+------------------------------------------------------------------+
//| Define o tipo de célula                                          |
//+------------------------------------------------------------------+
void CTable::CellType(const uint column_index,const uint row_index,const ENUM_TYPE_CELL type)
  {
//--- Verifica se o tamanho do array não excedeu
   if(!CheckOutOfRange(column_index,row_index))
      return;
//--- Define o tipo de célula
   m_columns[column_index].m_rows[row_index].m_type=type;
//--- Sinal de presença da caixa de edição de texto
   if(type==CELL_EDIT && !m_edit_state)
      m_edit_state=true;
//--- Sinal de presença da caixa de combinação
   else if(type==CELL_COMBOBOX && !m_combobox_state)
      m_combobox_state=true;
  }

Ao criar uma tabela, se for verificado que nenhuma célula do tipo CELL_EDIT ou CELL_COMBOBOX foram configurados, os controles dos tipos correspondentes não serão criados. Se necessário, os ponteiros para esses controles podem ser obtidos.

class CTable : public CElement
  {
private:
   //--- Objetos para a criação de uma tabela
   CTextEdit         m_edit;
   CComboBox         m_combobox;
   //---
private:
   bool              CreateEdit(void);
   bool              CreateCombobox(void);
   //---
public:
   //--- Retorna os ponteiros para os controles
   CTextEdit        *GetTextEditPointer(void)                { return(::GetPointer(m_edit));     }
   CComboBox        *GetComboboxPointer(void)                { return(::GetPointer(m_combobox)); }
  };

When handling a double click on the table, the CTable::CheckCellElement() method is called. Isto inclui as adições apropriadas às células dos tipos CELL_EDIT e CELL_COMBOBOX. O código a seguir mostra a versão resumida deste método. Os métodos para lidar com diferentes tipos de células serão descritos em detalhes abaixo.

//+------------------------------------------------------------------+
//| Verifica se o controle da célula foi ativado quando clicado      |
//+------------------------------------------------------------------+
bool CTable::CheckCellElement(const int column_index,const int row_index,const bool double_click=false)
  {
//--- Sai, se a célula não tem controles
   if(m_columns[column_index].m_rows[row_index].m_type==CELL_SIMPLE)
      return(false);
//---
   switch(m_columns[column_index].m_rows[row_index].m_type)
     {
      ...
      //--- Se esta é uma célula com uma caixa de edição de texto
      case CELL_EDIT :
        {
         if(!CheckPressedEdit(column_index,row_index,double_click))
            return(false);
         //---
         break;
        }
      //--- Se esta é uma célula com uma caixa de combinação
      case CELL_COMBOBOX :
        {
         if(!CheckPressedCombobox(column_index,row_index,double_click))
            return(false);
         //---
         break;
        }
     }
//---
   return(true);
  }

Antes de prosseguir com a consideração de métodos para lidar com os cliques nas células da tabela com controles, vamos refletir sobre as adições ao controle do tipo CTextBox. Às vezes, é necessário que todo o texto contido em uma caixa de texto seja automaticamente selecionada quando a caixa de texto for ativada e para que o cursor de texto seja movido para o final da linha. Isso facilita a inserção e substituição rápida de todo o texto. 

A seleção automática de texto na versão atual funciona apenas para uma caixa de texto de uma única linha. Este modo pode ser habilitado usando o método CTextBox::AutoSelectionMode(). 

//+------------------------------------------------------------------+
//| Classe para a criação de uma caixa de texto multilinha           |
//+------------------------------------------------------------------+
class CTextBox : public CElement
  {
private:
   //--- Modo de seleção automática de texto
   bool              m_auto_selection_mode;
   //---
public:
   //--- Modo de seleção automática de texto
   void              AutoSelectionMode(const bool state)       { m_auto_selection_mode=state;     }
  };

Um método privado CTextBox::SelectAllText() foi implementado para selecionar completamente o texto em uma caixa de texto. Aqui, o número de caracteres da primeira linha é obtido primeiro e os índices de seleção de texto são definidos. Em seguida, a área de texto visível deve ser movida para o lado direito. Finalmente, o cursor de texto deve ser movido para o final da linha.

class CTextBox : public CElement
  {
private:
   //--- Seleciona todo o texto
   void              SelectAllText(void);
  };
//+------------------------------------------------------------------+
//| Seleciona todo o texto                                           |
//+------------------------------------------------------------------+
void CTextBox::SelectAllText(void)
  {
//--- Obtém o tamanho do conjunto de caracteres
   int symbols_total=::ArraySize(m_lines[0].m_symbol);
//--- Define os índices para selecionar o texto
   m_selected_line_from   =0;
   m_selected_line_to     =0;
   m_selected_symbol_from =0;
   m_selected_symbol_to   =symbols_total;
//--- Move o indicador da barra de rolagem horizontal para a última posição
   HorizontalScrolling();
//--- Move o cursor para o final da linha
   SetTextCursor(symbols_total,0);
  }

A caixa de edição de texto aparecerá após o clique duplo em uma célula da tabela, mas para evitar que ocorra mais um clique para ativar a caixa de texto, é necessário haver um método público adicional CTextBox::ActivateTextBox(). Uma chamada a ele simula um clique na caixa de texto Para isso, basta chamar o método CTextBox::OnClickTextBox(), passando-o o nome do objeto gráfico do controle. A seleção do texto será realizada neste método.

class CTextBox : public CElement
  {
public:
   //--- Ativa a caixa de texto
   void              ActivateTextBox(void);
  };
//+------------------------------------------------------------------+
//| Ativa a caixa de texto                                           |
//+------------------------------------------------------------------+
void CTextBox::ActivateTextBox(void)
  {
   OnClickTextBox(m_textbox.Name());
  }

Uma vez que apenas uma caixa de texto será usada para toda a tabela, é necessário ter a capacidade de redimensioná-la, pois as células podem ter diferentes larguras. Portanto, foi adicionado um método público adicional CTextBox::ChangeSize(), na qual chama os métodos implementados anteriormente, considerados em outros artigos.

class CTextBox : public CElement
  {
public:
   //--- Redimensionamento
   void              ChangeSize(const uint x_size,const uint y_size);
  };
//+------------------------------------------------------------------+
//| Redimensionamento                                                |
//+------------------------------------------------------------------+
void CTextBox::ChangeSize(const uint x_size,const uint y_size)
  {
//--- Define o novo tamanho
   ChangeMainSize(x_size,y_size);
//--- Calcula o tamanho da caixa de texto
   CalculateTextBoxSize();
//--- Define o novo tamanho da caixa de texto
   ChangeTextBoxSize();
  }

O clique duplo em uma célula com uma caixa de texto chama o método CTable::CheckPressedEdit(). Também sera necessário aqui os campos da classe para armazenar os índices da última célula editada para processar o valor de entrada e o evento (ON_END_EDIT). 

Na versão atual, a caixa de edição de texto só será chamada clicando duas vezes em uma célula. Portanto, existe uma verificação no início do método. Em seguida, os índices passados ​​da coluna e da linha são armazenados. Para colocar a caixa de edição de texto acima da célula da tabela corretamente, é necessário calcular as coordenadas levando em consideração o deslocamento da tabela ao longo dos dois eixos. Além disso, a presença de cabeçalhos é levada em consideração nos cálculos. Depois disso, é necessário calcular e definir os tamanhos para a caixa de texto, e também inserir a string atual a ser exibida na célula. Em seguida, a caixa de texto é ativada e é tornada visível, o gráfico é redesenhado para refletir as últimas mudanças.

class CTable : public CElement
  {
private:
   //--- Índices da coluna e linha da última célula editada
   int               m_last_edit_row_index;
   int               m_last_edit_column_index;
   //---
private:
   //--- Verifica se o clique foi em uma célula com uma caixa de texto
   bool              CheckPressedEdit(const int column_index,const int row_index,const bool double_click=false);
  };
//+------------------------------------------------------------------+
//| Verifica se o clique foi na caixa de texto da célula             |
//+------------------------------------------------------------------+
bool CTable::CheckPressedEdit(const int column_index,const int row_index,const bool double_click=false)
  {
//--- Retorna, se não for um clique duplo
   if(!double_click)
      return(false);
//--- Armazena os índices
   m_last_edit_row_index    =row_index;
   m_last_edit_column_index =column_index;
//--- Desloca ao longo dos dois eixos
   int x_offset=(int)m_table.GetInteger(OBJPROP_XOFFSET);
   int y_offset=(int)m_table.GetInteger(OBJPROP_YOFFSET);
//--- Define as novas coordenadas
   m_edit.XGap(m_columns[column_index].m_x-x_offset);
   m_edit.YGap(m_rows[row_index].m_y+((m_show_headers)? m_header_y_size : 0)-y_offset);
//--- Tamanho
   int x_size =m_columns[column_index].m_x2-m_columns[column_index].m_x+1;
   int y_size =m_cell_y_size+1;
//--- Define o tamanho
   m_edit.GetTextBoxPointer().ChangeSize(x_size,y_size);
//--- Define o valor a partir da célula da tabela
   m_edit.SetValue(m_columns[column_index].m_rows[row_index].m_full_text);
//--- Ativa a caixa de texto
   m_edit.GetTextBoxPointer().ActivateTextBox();
//--- Define o foco
   m_edit.GetTextBoxPointer().MouseFocus(true);
//--- Exibe a caixa de texto
   m_edit.Reset();
//--- Redesenha o gráfico
   m_chart.Redraw();
   return(true);
  }

Uma vez que o valor é inserido na célula, um evento com o identificador ON_END_EDIT é gerado, que deve ser recebido no manipulador de eventos da tabela. O método CTable::OnEndEditCell() foi implementado para processar este evento. Se houver células com caixas de texto presentes e os identificadores coincidirem, então o novo valor é definido na célula da tabela. Depois disso, a caixa de texto deve ser desativada e ocultada.

class CTable : public CElement
  {
private:
   //--- Manipulação do final do valor de entrada na célula
   bool              OnEndEditCell(const int id);
  };
//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   ...
//--- Manipulação do evento final de entrada
   if(id==CHARTEVENT_CUSTOM+ON_END_EDIT)
     {
      if(OnEndEditCell((int)lparam))
         return;
      //---
      return;
     }
   ...
  }
//+------------------------------------------------------------------+
//| Manipulação do valor de entrada final na célula                  |
//+------------------------------------------------------------------+
bool CTable::OnEndEditCell(const int id)
  {
//--- Retorna, se (1) os identificadores não coincidirem ou (2) não há células com caixas de texto
   if(id!=CElementBase::Id() || !m_edit_state)
      return(false);
//--- Define o valor para a célula da tabela
   SetValue(m_last_edit_column_index,m_last_edit_row_index,m_edit.GetValue(),0,true);
   Update();
//--- Desativa e oculta a caixa de texto
   m_edit.GetTextBoxPointer().DeactivateTextBox();
   m_edit.Hide();
   m_chart.Redraw();
   return(true);
  }

O clique fora da caixa de texto ativada deve ocultar essa caixa de texto. Para fazer isso, é necessário o método CTable::OnEndEditCell(). Além disso, a caixa de texto deve ser desativada, para que ela seja exibida corretamente na próxima vez que for chamada. O método CTable::OnEndEditCell() é chamado no manipulador de eventos da tabela após a chegada do evento de alteração do estado do botão esquerdo do mouse (ON_CHANGE_MOUSE_LEFT_BUTTON). O mesmo princípio é usado no métodoCTable::CheckAndHideCombobox() para verificar se as caixas de combinação estão presentes nas células. O código deste método não será fornecido aqui, pois é quase idêntico ao que foi considerado.

class CTable : public CElement
  {
private:
   //--- Verifica se os controles nas células estão ocultos
   void              CheckAndHideEdit(void);
  };
//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   ...
//--- Altera o estado do botão esquerdo do mouse
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE_MOUSE_LEFT_BUTTON)
     {
      ...
      //--- Verifica se as caixas de texto nas células estão ocultas
      CheckAndHideEdit();
      //--- Verifica se as caixas de combinação nas células estão ocultas
      CheckAndHideCombobox();
      return;
     }
   ...
  }
//+------------------------------------------------------------------+
//| Verifica se as caixas de texto nas células estão ocultas         |
//+------------------------------------------------------------------+
void CTable::CheckAndHideEdit(void)
  {
//--- Retorna, se (1) não há caixa de texto ou (2) estiver ela estiver escondida
   if(!m_edit_state || !m_edit.IsVisible())
      return;
//--- Verifica o foco
   m_edit.GetTextBoxPointer().CheckMouseFocus();
//--- Desativa e oculta a caixa de texto, se (1) ela estiver fora de foco e (2) o botão esquerdo do mouse estiver pressionado
   if(!m_edit.GetTextBoxPointer().MouseFocus() && m_mouse.LeftButtonState())
     {
      m_edit.GetTextBoxPointer().DeactivateTextBox();
      m_edit.Hide();
      m_chart.Redraw();
     }
  }

Agora, vamos considerar como os métodos para a chamada da caixa de combinação de uma célula da tabela funcionam. Para as células do tipo CELL_COMBOBOX, será necessário um array para armazenar os valores da lista da caixa de combinação, bem como um campo adicional para armazenar o índice do item selecionado. O array e o campo foram adicionados à estrutura CTCell.

class CTable : public CElement
  {
private:
   //--- Propriedades das células da tabela
   struct CTCell
     {
      ...
      string            m_value_list[];   // Array de valores (para células com caixas de combinação)
      int               m_selected_item;  // Item selecionado na lista de caixa de combinação
      ...
     };
  };

Quando o tipo da caixa de combinação (CELL_COMBOBOX) é especificada para uma célula na classe personalizada antes de criar a tabela, é necessário também passar a lista de valores que serão passados ​​para a lista de caixa de combinação. 

Isso é feito pelo método CTable::AddValueList(). Este método também é passado no índice da célula e no índice do item a ser selecionado na lista da caixa de combinação. O primeiro item é selecionado por padrão (índice 0). 

Uma verificação de estouro de buffer está localizada no início do método. Depois disso, o array na estrutura CTCell é definido no mesmo tamanho da do array passado e realizado uma cópia dos valores. O índice do item selecionado é ajustado no caso de exceder o tamanho do array, que também se encontra armazenado na estrutura CTCell. O texto do item selecionado é definido para a célula.

class CTable : public CElement
  {
public:
   //--- Adiciona uma lista de valores à caixa de combinação
   void              AddValueList(const uint column_index,const uint row_index,const string &array[],const uint selected_item=0);
  };
//+------------------------------------------------------------------+
//| Adiciona uma lista de valores à caixa de combinação              |
//+------------------------------------------------------------------+
void CTable::AddValueList(const uint column_index,const uint row_index,const string &array[],const uint selected_item=0)
  {
//--- Verifica se o tamanho do array não excedeu
   if(!CheckOutOfRange(column_index,row_index))
      return;
//--- Define o tamanho da lista da célula especificada
   uint total=::ArraySize(array);
   ::ArrayResize(m_columns[column_index].m_rows[row_index].m_value_list,total);
//--- Armazena os valores passados
   ::ArrayCopy(m_columns[column_index].m_rows[row_index].m_value_list,array); 
//--- Verifica o índice do item selecionado na lista
   uint check_item_index=(selected_item>=total)? total-1 : selected_item;
//--- Armazena o item selecionado na lista
   m_columns[column_index].m_rows[row_index].m_selected_item=(int)check_item_index;
//--- Armazena o texto selecionado na célula
   m_columns[column_index].m_rows[row_index].m_full_text=array[check_item_index];
  }

O método CTable::CheckPressedCombobox() lida com um clique duplo em uma célula com uma caixa de combinação. Aqui, os índices das células são armazenados primeiramente para um processamento subsequente no caso de um item da lista for selecionado. Em seguida, as coordenadas da caixa de combinação são definidas em relação ao canto superior esquerdo da célula. Depois disso, seus controles são definidos nos mesmos tamanhos que a célula. Para redimensionar o botão (CButton) e a lista (CListView) durante o tempo de execução, o método ChangeSize() foi adicionado em suas classes, bem como nos dois campos. Uma vez que o tamanho da lista pode variar de célula para célula, é necessário reconstruir e reabastecer a lista toda vez. Em seguida, os elementos da caixa de combinação são redesenhados, ficando visíveis. No final do método, um evento relacionado a mudanças na interface gráfica é gerado

class CTable : public CElement
  {
private:
   //--- Verifica se o clique foi em uma célula com uma caixa de combinação
   bool              CheckPressedCombobox(const int column_index,const int row_index,const bool double_click=false);
  };
//+------------------------------------------------------------------+
//| Verifica se a caixa de combinação na célula foi clicada          |
//+------------------------------------------------------------------+
bool CTable::CheckPressedCombobox(const int column_index,const int row_index,const bool double_click=false)
  {
//--- Retorna, se não for um clique duplo
   if(!double_click)
      return(false);
//--- Armazena os índices
   m_last_edit_row_index    =row_index;
   m_last_edit_column_index =column_index;
//--- Desloca ao longo dos dois eixos
   int x_offset=(int)m_table.GetInteger(OBJPROP_XOFFSET);
   int y_offset=(int)m_table.GetInteger(OBJPROP_YOFFSET);
//--- Define as novas coordenadas
   m_combobox.XGap(m_columns[column_index].m_x-x_offset);
   m_combobox.YGap(m_rows[row_index].m_y+((m_show_headers)? m_header_y_size : 0)-y_offset);
//--- Define o tamanho do botão
   int x_size =m_columns[column_index].m_x2-m_columns[column_index].m_x+1;
   int y_size =m_cell_y_size+1;
   m_combobox.GetButtonPointer().ChangeSize(x_size,y_size);
//--- Define o tamanho da lista
   y_size=m_combobox.GetListViewPointer().YSize();
   m_combobox.GetListViewPointer().ChangeSize(x_size,y_size);
//--- Define o tamanho da lista de células
   int total=::ArraySize(m_columns[column_index].m_rows[row_index].m_value_list);
   m_combobox.GetListViewPointer().Rebuilding(total);
//--- Define a lista a partir da célula
   for(int i=0; i<total; i++)
      m_combobox.GetListViewPointer().SetValue(i,m_columns[column_index].m_rows[row_index].m_value_list[i]);
//--- Define o item a partir da célula
   int index=m_columns[column_index].m_rows[row_index].m_selected_item;
   m_combobox.SelectItem(index);
//--- Atualiza o controle
   m_combobox.GetButtonPointer().MouseFocus(true);
   m_combobox.GetButtonPointer().Update(true);
   m_combobox.GetListViewPointer().Update(true);
//--- Exibe a caixa de texto
   m_combobox.Reset();
//--- Redesenha o gráfico
   m_chart.Redraw();
//--- Envia uma mensagem sobre a alteração na interface gráfica
   ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,"");
   return(true);
  }

O evento de selecionar um item na lista da caixa de combinação (ON_CLICK_COMBOBOX_ITEM) é tratada pelo método CTable::OnClickComboboxItem(). Aqui, primeiro é verificado se os identificadores coincidem e se uma caixa de combinação está presente na tabela. Se essas verificações forem satisfeitas, então o índice do item selecionado e o valor do item é definido na célula de acordo com os índices armazenados anteriormente.

class CTable : public CElement
  {
private:
   //--- Manipulando a seleção de um item na lista suspensa da célula
   bool              OnClickComboboxItem(const int id);
  };
//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   ...
//--- Manipulando o evento de selecionar um item na lista
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
     {
      if(OnClickComboboxItem((int)lparam))
         return;
      //---
      return;
     }
   ...
  }
//+------------------------------------------------------------------+
//| Manipulação da seleção de um item na caixa de combinação da célula|
//+------------------------------------------------------------------+
bool CTable::OnClickComboboxItem(const int id)
  {
//--- Retorna, se (1) os identificadores não coincidirem ou (2) não tiver células com caixas de combinação
   if(id!=CElementBase::Id() || !m_combobox_state)
      return(false);
//--- Índices da última célula editada
   int c=m_last_edit_column_index;
   int r=m_last_edit_row_index;
//--- Armazena o índice do item selecionado na célula
   m_columns[c].m_rows[r].m_selected_item=m_combobox.GetListViewPointer().SelectedItemIndex();
//--- Define o valor para a célula da tabela
   SetValue(c,r,m_combobox.GetValue(),0,true);
   Update();
   return(true);
  }

No final, tudo ficará assim:

Fig. 6. Demonstração do trabalho com caixas de texto e caixas de combinação nas células da tabela. 

Fig. 6. Demonstração do trabalho com caixas de texto e caixas de combinação nas células da tabela.

Aplicação para testes

Para fins de teste, foi criado uma aplicação MQL, que contém os controles da tabela (CTable) e da caixa de texto multilinha (CTextBox). Na primeira coluna da tabela, todas as células contêm o controle caixa de seleção (CELL_CHECKBOX). Na segunda coluna da tabela, as células têm o tipo "caixa de texto" (CELL_EDIT). Na terceira coluna, as células são alternadamente configuradas para os tipos "caixa de combinação" (CELL_COMBOBOX) e "caixa de texto" (CELL_EDIT). Na quinta coluna, as células têm o tipo "botão" (CELL_BUTTON). O manipulador de eventos da classe personalizada da aplicação MQL processará e exibirá os eventos na caixa de texto multilinha. 

É assim que ele funciona:

 Fig. 7. Aplicação MQL para testar o trabalho realizado.

Fig. 7. Aplicação MQL para testar o trabalho realizado.

Esta aplicação está disponível no arquivo anexado no final do artigo para um estudo mais detalhado.

Conclusão

A tabela agora tem a capacidade de criar células dos tipos "caixa de texto" e "caixa de combinação". O formulário para controles agora pode ser expandido para a tela cheia ou redimensionado manualmente arrastando suas bordas.

Estrutura da biblioteca no atual estágio de desenvolvimento:

 Fig. 8. Estrutura da biblioteca, no atual estágio de desenvolvimento

Fig. 8. Estrutura da biblioteca, no atual estágio de desenvolvimento

O código da biblioteca apresentado é gratuito. Você pode usá-lo em seus projetos, inclusive comercialmente, escrever artigos e cumprir ordens.

Se você tiver dúvidas sobre como usar o material do artigo, você pode perguntar nos comentários.


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

Arquivos anexados |
Usando armazenamento em nuvem para intercâmbio de dados entre os terminais Usando armazenamento em nuvem para intercâmbio de dados entre os terminais
As tecnologias baseadas em nuvem estão se tornando mais populares. À nossa disposição temos serviços de armazenamento pagos ou gratuitos. Mas será que é possível usá-los na negociação? Este artigo apresenta uma tecnologia para intercâmbio de dados entre terminais usando serviços de armazenamento em nuvem.
Criação e teste de símbolos personalizados na MetaTrader 5 Criação e teste de símbolos personalizados na MetaTrader 5
A criação de símbolos personalizados empurra os limites no desenvolvimento de sistemas de negociação e análise do mercado financeiro. Agora, os traders são capazes de desenhar gráficos e testar estratégias de negociação em um número ilimitado de instrumentos financeiros.
Redes Neurais Profundas (Parte III). Seleção da amostra e redução de dimensionalidade Redes Neurais Profundas (Parte III). Seleção da amostra e redução de dimensionalidade
Este artigo é uma continuação da série de artigos sobre redes neurais profundas. Aqui, nós vamos considerar a seleção de amostras (remoção de ruído), reduzindo a dimensionalidade dos dados de entrada e dividindo o conjunto de dados nos conjuntos de train/val/test durante a preparação dos dados para treinar a rede neural.
Examinemos na prática o método adaptativo de acompanhamento do mercado Examinemos na prática o método adaptativo de acompanhamento do mercado
A principal diferença entre ele e o sistema de negociação proposto no artigo é o uso de ferramentas matemáticas para analisar as cotações da bolsa de valores. O sistema implementa filtragem digital e estimativa espectral de séries temporais discretas. Descrevem-se os aspectos teóricos da estratégia e constrói-se o Expert Advisor para testá-la.