Interfaces Gráficas X: Os Controles Horário, Lista de Caixas de Seleção e Tabela Ordenada (build 6)

23 fevereiro 2017, 09:00
Anatoli Kazharski
0
591

Conteúdo


Introdução

A fim de obter uma melhor compreensão do propósito desta biblioteca, leia por favor o primeiro artigo: Interfaces Gráficas I: Preparação da Estrutura da Biblioteca (Capítulo 1). Você irã encontrar uma lista de artigos com os links no final de cada capítulo. Lá, você também pode encontrar e baixar a versão completa da biblioteca, no estágio de desenvolvimento atual. Os arquivos devem estar localizados nas mesmas pastas que o arquivo baixado.

A biblioteca continua a crescer. Será discutido primeiramente os controles Horário e Lista de Caixas de Seleção. Além disso, agora a classe CTable oferece a capacidade de classificar os dados em ordem crescente ou decrescente. Esta e outras alterações serão descritas neste artigo.

 

O Controle Horário

Às vezes pode ser necessário especificar os intervalos de tempo ao criar uma interface gráfica para um indicador ou expert. Às vezes, essa necessidade surge durante a negociação intra-diária. O Calendário e o Calendário Suspenso foram demonstrados, podendo ser usados ​​para definir uma data, mas não o horário (horas e minutos).

Vamos enumerar todos os componentes do controle Horário:

  • Fundo
  • Ícone
  • Descrição
  • Duas Caixas de Edição

Fig. 1. Componentes do Controle Horário.

Fig. 1. Componentes do Controle Horário.

 

Vamos dar uma olhada mais a fundo na classe deste controle.

 

Classe para a criação do Controle Horário

Crie o arquivo TimeEdit.mqh com a classe CTimeEdit que possui os métodos padrão para todos os controles e inclua-o no motor da biblioteca (o arquivo WndContainer.mqh). Abaixo estão as propriedades do controle que estão disponíveis para o usuário personalizar.

  • Cor de fundo do controle
  • Ícones do controle para os estados ativo e bloqueado
  • Margens do ícone ao longo dos dois eixos (x, y)
  • Descrição de texto do controle
  • Margens para o rótulo de texto ao longo dos dois eixos (x, y)
  • Cor do texto em diferentes estados do controle
  • Largura das Caixas de Edição
  • Margens para as Caixas de Edição ao longo dos dois eixos (x, y)
  • Estado do controle (disponível/bloqueado)
  • Modo de resetar os valores nas Caixas de Edição
//+------------------------------------------------------------------+
//| Classe para criar o controle Horário                             |
//+------------------------------------------------------------------+
class CTimeEdit : public CElement
  {
private:
   //--- Cor de fundo do controle
   color             m_area_color;
   //--- Ícones da caixa de seleção nos estados ativo e bloqueado
   string            m_icon_file_on;
   string            m_icon_file_off;
   //--- Margens do ícone
   int               m_icon_x_gap;
   int               m_icon_y_gap;
   //--- Descrição do texto do controle
   string            m_label_text;
   //--- Margens do rótulo de texto
   int               m_label_x_gap;
   int               m_label_y_gap;
   //--- Cor do texto em diferentes estados
   color             m_label_color;
   color             m_label_color_hover;
   color             m_label_color_locked;
   color             m_label_color_array[];
   //--- Tamanho da Caixa de edição
   int               m_edit_x_size;
   //--- Margens de caixa Editar
   int               m_edit_x_gap;
   int               m_edit_y_gap;
   //--- Estado do controle (disponível/bloqueado)
   bool              m_time_edit_state;
   //--- O modo de resetar o valor
   bool              m_reset_mode;
   //---
public:
   //--- (1) Cor de fundo, (2) margens para o ícone
   void              AreaColor(const color clr)                     { m_area_color=clr;                  }
   void              IconXGap(const int x_gap)                      { m_icon_x_gap=x_gap;                }
   void              IconYGap(const int y_gap)                      { m_icon_y_gap=y_gap;                }
   //--- (1) Texto da descrição do controle, (2) margens para o rótulo de texto
   string            LabelText(void)                          const { return(m_label.Description());     }
   void              LabelText(const string text)                   { m_label.Description(text);         }
   void              LabelXGap(const int x_gap)                     { m_label_x_gap=x_gap;               }
   void              LabelYGap(const int y_gap)                     { m_label_y_gap=y_gap;               }
   //--- Colors of the text label in different states
   void              LabelColor(const color clr)                    { m_label_color=clr;                 }
   void              LabelColorHover(const color clr)               { m_label_color_hover=clr;           }
   void              LabelColorLocked(const color clr)              { m_label_color_locked=clr;          }
   //--- (1) Tamanho da Caixa de edição, (2) margens para as Caixas de Edição
   void              EditXSize(const int x_size)                    { m_edit_x_size=x_size;              }
   void              EditXGap(const int x_gap)                      { m_edit_x_gap=x_gap;                }
   void              EditYGap(const int y_gap)                      { m_edit_y_gap=y_gap;                }
   //--- (1) Reseta o modo de reajuste ao pressionar o rótulo de texto, (2) modo de seleção de texto
   bool              ResetMode(void)                                { return(m_reset_mode);              }
   void              ResetMode(const bool mode)                     { m_reset_mode=mode;                 }
  };

Para criar o controle Horário, serão necessários cinco métodos privados e um público. Este controle é composto e os controles CSpinEdit prontos serão usados como caixas de edição. 

class CTimeEdit : public CElement
  {
private:
   //--- Objetos para criar o controle
   CRectLabel        m_area;
   CBmpLabel         m_icon;
   CLabel            m_label;
   CSpinEdit         m_hours;
   CSpinEdit         m_minutes;

   //---
public:
   //--- Métodos para criar o controle
   bool              CreateTimeEdit(const long chart_id,const int subwin,const string label_text,const int x_gap,const int y_gap);
   //---
private:
   bool              CreateArea(void);
   bool              CreateIcon(void);
   bool              CreateLabel(void);
   bool              CreateHoursEdit(void);
   bool              CreateMinutesEdit(void);
   //---
public:
   //--- Retorna os ponteiros para as caixas de edição
   CSpinEdit        *GetHoursEditPointer(void)                      { return(::GetPointer(m_hours));     }
   CSpinEdit        *GetMinutesEditPointer(void)                    { return(::GetPointer(m_minutes));   }

  };

A fim de obter programaticamente e definir os valores atuais nas caixas de edição (horas e minutos), use os métodos do código a seguir: 

class CTimeEdit : public CElement
  {
public:
   //--- Obtém e define os valores de caixa de edição
   int               GetHours(void)                           const { return((int)m_hours.GetValue());   }
   int               GetMinutes(void)                         const { return((int)m_minutes.GetValue()); }
   void              SetHours(const uint value)                     { m_hours.ChangeValue(value);        }
   void              SetMinutes(const uint value)                   { m_minutes.ChangeValue(value);      }
  };

Mais abaixo, será fornecido um exemplo de como este controle se parece no gráfico do terminal. 


O Controle Lista de Caixas de Seleção

Um dos artigos anteriores discutiram o controle Lista (classe CListView), que poderá ser utilizada para selecionar um item da lista fornecida. Mas às vezes você poderá precisar selecionar vários itens. Por exemplo, pode ser necessário criar uma lista de símbolos ou períodos, onde o usuário do aplicativo MQL pode escolher apenas o que for necessário para a sua negociação.

Lista dos componentes para a criação do controle lista de caixas de seleção:

  1. Fundo comum do controle
  2. Barra de rolagem vertical
  3. Grupo da caixa de seleção:
    • Fundo
    • Ícone
    • Rótulo de texto

Fig. 2. Componentes do controle Lista de caixas de seleção

Fig. 2. Componentes do controle Lista de caixas de seleção

 

Em seguida, nós vamos considerar brevemente as diferenças entre este tipo de lista (CCheckBoxList) e a lista simples (CListView), que foi discutida anteriormente.

 

Classe para a criação do Controle Lista de Caixas de Seleção

Crie o arquivo CheckBoxList.mqh com a classe CCheckBoxList, contendo os métodos padrão para todos os controles da biblioteca. A primeira diferença de uma lista do tipo CListView é que os itens da lista são feitos de três objetos gráficos (Veja o código abaixo). Um método privado separado é criado para cada tipo de objeto.

//+------------------------------------------------------------------+
//| Classe para a criação da Lista de caixas de seleção              |
//+------------------------------------------------------------------+
class CCheckBoxList : public CElement
  {
private:
   //--- Objetos para a criação da lista
   CRectLabel        m_area;
   CEdit             m_items[];
   CBmpLabel         m_checks[];
   CLabel            m_labels[];

   CScrollV          m_scrollv;
   //---
public:
   //--- Métodos para a criação do controle
   bool              CreateCheckBoxList(const long chart_id,const int subwin,const int x_gap,const int y_gap);
   //---
private:
   bool              CreateArea(void);
   bool              CreateList(void);
   bool              CreateItems(void);
   bool              CreateChecks(void);
   bool              CreateLabels(void);

   bool              CreateScrollV(void);
  };

Além dos valores do item (descrições dos itens), será necessário também de um array de estados da caixa de seleção (ligado/desligado). Será necessário também os métodos para definir e obter os valores do índice especificado na lista: 

class CCheckBoxList : public CElement
  {
private:
   //--- Array de valores e estados das caixas de seleção na lista
   string            m_item_value[];
   bool              m_item_state[];
   //---
public:
   //--- Retorna/armazena o (1) estado e (2) o texto do item da lista no índice especificado
   void              SetItemState(const uint item_index,const bool state);
   void              SetItemValue(const uint item_index,const string value);
   bool              GetItemState(const uint item_index);
   string            GetItemValue(const uint item_index);
  };
//+------------------------------------------------------------------+
//| Definindo o estado                                               |
//+------------------------------------------------------------------+
void CCheckBoxList::SetItemState(const uint item_index,const bool state)
  {
   uint array_size=::ArraySize(m_item_state);
//--- Se não houver nenhum item na lista, reporta
   if(array_size<1)
      ::Print(__FUNCTION__," > Este método é para ser chamado, caso a lista tenha pelo menos um item!");
//--- Ajuste no caso do intervalo ter sido excedido
   uint check_index=(item_index>=array_size)? array_size-1 : item_index;
//--- Armazena o valor
   m_item_state[check_index]=state;
//--- Move a lista junto a barra de rolagem
   ShiftList();
  }
//+------------------------------------------------------------------+
//| Obtém o estado da lista da caixa de seleção                      |
//+------------------------------------------------------------------+
bool CCheckBoxList::GetItemState(const uint item_index)
  {
   uint array_size=::ArraySize(m_item_state);
//--- Se não houver nenhum item na lista, reporta
   if(array_size<1)
      ::Print(__FUNCTION__," > Este método é para ser chamado, se a lista tem pelo menos um item!");
//--- Ajuste no caso do intervalo ter sido excedido
   uint check_index=(item_index>=array_size)? array_size-1 : item_index;
//--- Armazena o valor
   return(m_item_state[check_index]);
  }

As alterações apropriadas foram feitas com os métodos relacionados para o controle da lista. Você pode avaliar eles por si próprio. 


A Tabela Ordenada

Se a interface gráfica de uma aplicação utiliza tabelas com dados, pode ser que algumas vezes seja necessário ordená-las de acordo com a coluna especificada pelo usuário. Em muitas implementações das interfaces gráficas isso é implementado de tal forma, que os dados são ordenados clicando no cabeçalho da coluna. O primeiro clique no cabeçalho classifica os dados em ordem crescente, do valor mínimo até o máximo. O segundo clique classifica os dados em ordem decrescente, ou seja, do valor máximo até o mínimo.

A imagem abaixo mostra a janela Caixa de Ferramentas do MetaEditor com uma tabela de três colunas. A tabela é ordenada (descendente) de acordo com a terceira coluna, que contém as datas.

Fig. 3. Exemplo de uma tabela com os dados ordenados

Fig. 3. Exemplo de uma tabela com os dados ordenados

 

Uma seta é tipicamente mostrada no cabeçalho da coluna como um sinal de ordenação dos dados. Se ele é direcionado para baixo, como na imagem acima, então, os dados são classificados em ordem decrescente, e vice-versa.

Assim, será feito a ordenação da tabela do tipo CTable neste artigo. Ela já contém a capacidade de ativar cabeçalhos para as colunas, eles não precisam ser feitos de forma interativa. Em primeiro lugar, é necessário deixar os cabeçalhos alterando a sua cor quando o mouse estiver pairado sobre ele, e quando ele for clicado. Para fazer isso, adicione os campos e métodos para a classe CTable para definir as cores de cabeçalhos de coluna em diferentes estados (veja o código abaixo).

class CTable : public CElement
  {
private:
   //--- Cor de fundo do cabeçalho
   color             m_headers_color;
   color             m_headers_color_hover;
   color             m_headers_color_pressed;

   //---
public:
   //--- Cores de fundo do cabeçalho
   void              HeadersColor(const color clr)                              { m_headers_color=clr;                     }
   void              HeadersColorHover(const color clr)                         { m_headers_color_hover=clr;               }
   void              HeadersColorPressed(const color clr)                       { m_headers_color_pressed=clr;             }

  };

Cabe ao usuário decidir se o recurso de ordenação é necessário na tabela. O modo de ordenação será desativado por padrão. Para habilitá-lo, use o método CTable::IsSortMode(): 

class CTable : public CElement
  {
private:
   //--- Modo de ordenação dos dados de acordo com as colunas
   bool              m_is_sort_mode;
   //---
public:
   //--- Modo de ordenação dos dados
   void              IsSortMode(const bool flag)                                { m_is_sort_mode=flag;                     }
  };

O método privado CTable::HeaderColorByHover() será usado para alterar as cores de cabeçalho quando o mouse estiver pairado sobre ele. Ele é chamado no manipulador de eventos do controle. 

class CTable : public CElement
  {
private:
   //--- Alterando a cor do cabeçalho da tabela, quando o cursor do mouse estiver pairado sobre ele
   void              HeaderColorByHover(void);
  };
//+------------------------------------------------------------------+
//| Alterando a cor do cabeçalho da tabela, quando o cursor do mouse estiver pairado sobre ele |
//+------------------------------------------------------------------+
void CTable::HeaderColorByHover(void)
  {
//--- Sai, se o modo de ordenação da coluna está desativado
   if(!m_is_sort_mode || !m_fix_first_row)
      return;
//---
   for(uint c=0; c<m_visible_columns_total; c++)
     {
      //--- Verifica o foco no cabeçalho atual
      bool condition=m_mouse.X()>m_columns[c].m_rows[0].X() && m_mouse.X()<m_columns[c].m_rows[0].X2() &&
                     m_mouse.Y()>m_columns[c].m_rows[0].Y() && m_mouse.Y()<m_columns[c].m_rows[0].Y2();
      //---
      if(!condition)
         m_columns[c].m_rows[0].BackColor(m_headers_color);
      else
        {
         if(!m_mouse.LeftButtonState())
            m_columns[c].m_rows[0].BackColor(m_headers_color_hover);
         else
            m_columns[c].m_rows[0].BackColor(m_headers_color_pressed);
        }
     }
  }

Para criar um ícone de sinal para os dados ordenados, é necessário adicionar o método privado CTable::CreateSignSortedData(). Se a ordenação não foi habilitada antes da criação da tabela, o ícone não será criado. Se o modo de ordenação está habilitado, o ícone ficará oculto logo após a sua criação, já que inicialmente os dados da tabela não estão ordenados. 

class CTable : public CElement
  {
private:
   //--- Objetos para a criação de uma tabela
   CBmpLabel         m_sort_arrow;
   //---
private:
   //--- Métodos para a criação de uma tabela
   bool              CreateSignSortedData(void);
  };
//+------------------------------------------------------------------+
//| Cria um ícone de seta como um sinal que os dados estão ordenados |
//+------------------------------------------------------------------+
bool CTable::CreateSignSortedData(void)
  {
//--- Sai, se o modo de classificação está desativado
   if(!m_is_sort_mode)
      return(true);

//--- Formando o nome do objeto
   string name=CElement::ProgramName()+"_table_sort_array_"+(string)CElement::Id();
//--- Coordenadas
   int x =(m_anchor_right_window_side)? m_columns[0].m_rows[0].X()-m_columns[0].m_rows[0].XSize()+m_sort_arrow_x_gap : m_columns[0].m_rows[0].X2()-m_sort_arrow_x_gap;
   int y =(m_anchor_bottom_window_side)? CElement::Y()-m_sort_arrow_y_gap : CElement::Y()+m_sort_arrow_y_gap;
//--- Se o ícone da seta não for especificada, então, define o padrão
   if(m_sort_arrow_file_on=="")
      m_sort_arrow_file_on="Images\\EasyAndFastGUI\\Controls\\SpinInc.bmp";
   if(m_sort_arrow_file_off=="")
      m_sort_arrow_file_off="Images\\EasyAndFastGUI\\Controls\\SpinDec.bmp";
//--- Define o objeto
   if(!m_sort_arrow.Create(m_chart_id,name,m_subwin,x,y))
      return(false);
//--- Define as propriedades
   m_sort_arrow.BmpFileOn("::"+m_sort_arrow_file_on);
   m_sort_arrow.BmpFileOff("::"+m_sort_arrow_file_off);
   m_sort_arrow.Corner(m_corner);
   m_sort_arrow.GetInteger(OBJPROP_ANCHOR,m_anchor);
   m_sort_arrow.Selectable(false);
   m_sort_arrow.Z_Order(m_zorder);
   m_sort_arrow.Tooltip("\n");
//--- Armazena as coordenadas
   m_sort_arrow.X(x);
   m_sort_arrow.Y(y);
//--- Armazena os tamanhos (em objeto)
   m_sort_arrow.XSize(m_sort_arrow.X_Size());
   m_sort_arrow.YSize(m_sort_arrow.Y_Size());
//--- Margens da borda
   m_sort_arrow.XGap((m_anchor_right_window_side)? x : x-m_wnd.X());
   m_sort_arrow.YGap((m_anchor_bottom_window_side)? y : y-m_wnd.Y());
//--- Oculta o objeto
   m_sort_arrow.Timeframes(OBJ_NO_PERIODS);
//--- Armazena o ponteiro do objeto
   CElement::AddToArray(m_sort_arrow);
   return(true);
  }

A estrutura para os valores e as propriedades das células da tabela devem ser complementados por um array com o número de casas decimais para cada célula, se esses são números reais, e também por um campo com o tipo de dados para cada coluna da tabela.

class CTable : public CElement
  {
private:
   //--- Array de valores da tabela e suas propriedades
   struct TOptions
     {
      ENUM_DATATYPE     m_type;
      string            m_vrows[];
      uint              m_digits[];
      ENUM_ALIGN_MODE   m_text_align[];
      color             m_text_color[];
      color             m_cell_color[];
     };
   TOptions          m_vcolumns[];
  };

Ao inserir um valor em uma célula da tabela, o número de casas decimais é definido como zero por padrão: 

class CTable : public CElement
  {
public:
   //--- Define o valor para a célula da tabela especificada
   void              SetValue(const uint column_index,const uint row_index,const string value="",const uint digits=0);
  };

Para definir o tipo de dados em uma coluna da tabela em particular, bem como para obter o tipo, utilize o método CTable::DataType():

class CTable : public CElement
  {
public:
   //--- Obtém/define o tipo de dados
   ENUM_DATATYPE     DataType(const uint column_index)                          { return(m_vcolumns[column_index].m_type); }
   void              DataType(const uint column_index,const ENUM_DATATYPE type) { m_vcolumns[column_index].m_type=type;    }
  };

Antes de criar uma tabela, o número total de colunas e linhas devem ser especificadas. O campo m_type e o array m_digits são inicializados com os valores padrão. Inicialmente, todas as colunas têm o tipo string (TYPE_STRING) e o número de casas decimais em todas as células é zero

//+------------------------------------------------------------------+
//| Define o tamanho da tabela                                       |
//+------------------------------------------------------------------+
void CTable::TableSize(const uint columns_total,const uint rows_total)
  {
//--- Deve haver pelo menos uma coluna
   m_columns_total=(columns_total<1) ? 1 : columns_total;
//--- Deve haver pelo menos duas linhas
   m_rows_total=(rows_total<2) ? 2 : rows_total;
//--- Define o tamanho do aray de colunas
   ::ArrayResize(m_vcolumns,m_columns_total);
//--- Ajusta o tamanho das linhas dos arrays
   for(uint i=0; i<m_columns_total; i++)
     {
      ::ArrayResize(m_vcolumns[i].m_vrows,m_rows_total);
      ::ArrayResize(m_vcolumns[i].m_digits,m_rows_total);
      ::ArrayResize(m_vcolumns[i].m_text_align,m_rows_total);
      ::ArrayResize(m_vcolumns[i].m_text_color,m_rows_total);
      ::ArrayResize(m_vcolumns[i].m_cell_color,m_rows_total);
      //--- Inicializo o array de cores de fundo da célula com o valor padrão
      m_vcolumns[i].m_type=TYPE_STRING;
      ::ArrayInitialize(m_vcolumns[i].m_digits,0);
      ::ArrayInitialize(m_vcolumns[i].m_text_align,m_align_mode);
      ::ArrayInitialize(m_vcolumns[i].m_cell_color,m_cell_color);
      ::ArrayInitialize(m_vcolumns[i].m_text_color,m_cell_text_color);
     }
  }

Serão necessários vários métodos privados para classificar os arrays da tabela, onde as seguintes operações serão realizadas:

  • O algoritmo de ordenação.
  • Comparação dos valores na condição especificada.
  • Trocar os valores dos elementos do array.

Trocar os valores dos elementos do array utilizando o método CTable::Swap(). Aqui, a troca é feita diretamente por linhas da tabela. Não são apenas os valores das células que são trocados, mas também a cor do texto. 

class CTable : public CElement
  {
private:
   //--- Troca os valores nas células especificadas
   void              Swap(uint c,uint r1,uint r2);
  };
//+------------------------------------------------------------------+
//| Troca os elementos                                               |
//+------------------------------------------------------------------+
void CTable::Swap(uint c,uint r1,uint r2)
  {
//--- Itera sobre todas as colunas em um loop
   for(uint i=0; i<m_columns_total; i++)
     {
      //--- Troca o texto
      string temp_text          =m_vcolumns[i].m_vrows[r1];
      m_vcolumns[i].m_vrows[r1] =m_vcolumns[i].m_vrows[r2];
      m_vcolumns[i].m_vrows[r2] =temp_text;
      //--- Troca a cor do texto
      color temp_text_color          =m_vcolumns[i].m_text_color[r1];
      m_vcolumns[i].m_text_color[r1] =m_vcolumns[i].m_text_color[r2];
      m_vcolumns[i].m_text_color[r2] =temp_text_color;
     }
  }

Para a ordenação ser correta, os valores devem ser comparados utilizando seleção do tipo especificado no campo m_type. Para este efeito, foi criado o método CTable::CheckSortCondition() separado.

class CTable : public CElement
  {
private:
   //--- Verificação das condições de classificação
   bool              CheckSortCondition(uint column_index,uint row_index,const string check_value,const bool direction);
  };
//+------------------------------------------------------------------+
//| Comparando os valores na condição de classificação especificada  |
//+------------------------------------------------------------------+
//| direção: true (>), false (<)                                     |
//+------------------------------------------------------------------+
bool CTable::CheckSortCondition(uint column_index,uint row_index,const string check_value,const bool direction)
  {
   bool condition=false;
//---
   switch(m_vcolumns[column_index].m_type)
     {
      case TYPE_STRING :
        {
         string v1=m_vcolumns[column_index].m_vrows[row_index];
         string v2=check_value;
         condition=(direction)? v1>v2 : v1<v2;
         break;
        }
      //---
      case TYPE_DOUBLE :
        {
         double v1=double(m_vcolumns[column_index].m_vrows[row_index]);
         double v2=double(check_value);
         condition=(direction)? v1>v2 : v1<v2;
         break;
        }
      //---
      case TYPE_DATETIME :
        {
         datetime v1=::StringToTime(m_vcolumns[column_index].m_vrows[row_index]);
         datetime v2=::StringToTime(check_value);
         condition=(direction)? v1>v2 : v1<v2;
         break;
        }
      //---
      default :
        {
         long v1=(long)m_vcolumns[column_index].m_vrows[row_index];
         long v2=(long)check_value;
         condition=(direction)? v1>v2 : v1<v2;
         break;
        }
     }
//---
   return(condition);
  }

Os métodos CTable::Swap() e CTable::CheckSortCondition() serão utilizados no método com o algoritmo de ordenação. Vamos verificar qual algoritmo em particular foi selecionado para ordenar os dados. Dez algoritmos foram testados, incluindo a ordenação MQL padrão usando a função ArraySort():

  • Ordenação MQL
  • Ordenação por seleção (Selection sort)
  • Ordenação por flutuação (Bubble sort)
  • Ordenação de Cocktail
  • Ordenação por inserção (Insertion sort)
  • Ordenação de shell (Shellsort)
  • Ordenação Binária
  • Quicksort
  • Heapsort
  • Mergesort

Esses métodos foram testados em um array com 100.000 (cem mil) elementos. O array foi inicializado com números aleatórios. Os resultados do teste são mostrados no histograma abaixo:

Fig. 4. Gráfico dos resultados do teste de diferentes métodos de ordenação. O tamanho do array é de 100.000 elementos.

Fig. 4. Gráfico dos resultados do teste de diferentes métodos de ordenação. O tamanho do array é de 100.000 elementos.


O método quicksort é provado ser o mais rápido. O código deste método foi feito a partir da biblioteca padrão - o método QuickSort(). Neste teste, ele mostrou um resultado melhor que a função ArraySort(). Os tempos de operação do algoritmo foram medidos utilizando a função GetMicrosecondCount() para uma maior precisão. Deixamos apenas os algoritmos que mostraram os melhores resultados (menor do que um segundo). 

Fig. 5. Gráfico dos melhores resultados do teste dos métodos de ordenação, o tamanho do array é de 100.000 elementos.

Fig. 5. Gráfico dos melhores resultados do teste dos métodos de ordenação, o tamanho do array é de 100.000 elementos.


Aumenta o tamanho do array por 10 vezes, ou seja, agora uma série de 1.000.000 (um milhão) de elementos serão ordenados.

Fig. 6. Gráfico dos resultados do teste dos métodos de ordenação, o tamanho do array é 1.000.000 elementos.

Fig. 6. Gráfico dos resultados do teste dos métodos de ordenação, o tamanho do array é 1.000.000 elementos.


O algoritmo padrão - a função ArraySort() era a melhor neste teste. O método quicksort provou-se ser ligeiramente pior, por conseguinte, ele será selecionado. A função ArraySort() não é adequada para a tarefa, já que: (1) é necessário ter a capacidade de ordenar nos dois sentidos, (2) a classe CTable utiliza um array de estruturas e (3) é necessário controlar a localização de não apenas os valores nas células da tabela, mas as suas outras propriedades também. 

A enumeração ENUM_SORT_MODE deve ser adicionada ao arquivo Enums.mqh para os dois sentidos da ordenação:

  • SORT_ASCEND - ascendente.
  • SORT_DESCEND - descendente.
//+------------------------------------------------------------------+
//| Enumeração dos modos de ordenação                                |
//+------------------------------------------------------------------+
enum ENUM_SORT_MODE
  {
   SORT_ASCEND  =0,
   SORT_DESCEND =1
  };

A versão atual do algoritmo quicksort utilizado no método CTable::QuickSort() é mostrado no código abaixo. Os métodos CTable::Swap() e CTable::CheckSortCondition(), apresentados anteriormente neste artigo, estão destacados em amarelo. 

class CTable : public CElement
  {
private:
   //--- Método Quicksort
   void              QuickSort(uint beg,uint end,uint column,const ENUM_SORT_MODE mode=SORT_ASCEND);
  };
//+------------------------------------------------------------------+
//| Algoritmo Quicksort                                              |
//+------------------------------------------------------------------+
void CTable::QuickSort(uint beg,uint end,uint column,const ENUM_SORT_MODE mode=SORT_ASCEND)
  {
   uint   r1         =beg;
   uint   r2         =end;
   uint   c          =column;
   string temp       =NULL;
   string value      =NULL;
   uint   data_total =m_rows_total-1;
//--- Executa o algoritmo enquanto o índice esquerdo for menor do que o índice mais a direita
   while(r1<end)
     {
      //--- Obtém o valor a partir do meio da fila
      value=m_vcolumns[c].m_vrows[(beg+end)>>1];
      //--- Executa o algoritmo enquanto o indicador esquerdo for menor que o índice direito encontrado
      while(r1<r2)
        {
         //--- Desloca o índice para a direita enquanto estiver encontrando o valor na condição especificada
         while(CheckSortCondition(c,r1,value,(mode==SORT_ASCEND)? false : true))
           {
            //--- Verifica se o intervalo do array excedeu
            if(r1==data_total)
               break;
            r1++;
           }
         //--- Desloca o índice para a esquerda enquanto encontra o valor na condição especificada
         while(CheckSortCondition(c,r2,value,(mode==SORT_ASCEND)? true : false))
           {
            //--- Verifica se o intervalo do array excedeu
            if(r2==0)
               break;
            r2--;
           }
         //--- Se o índice esquerdo ainda não for maior do que o índice direito
         if(r1<=r2)
           {
            //--- Troca os valores
            Swap(c,r1,r2);
            //--- Se o limite da esquerda foi atingido
            if(r2==0)
              {
               r1++;
               break;
              }
            //---
            r1++;
            r2--;
           }
        }
      //--- Continuação recursiva do algoritmo, até o início do intervalo ser atingido
      if(beg<r2)
         QuickSort(beg,r2,c,mode);
      //--- Limita o intervalo para a próxima iteração
      beg=r1;
      r2=end;
     }
  }

Todos estes métodos são privados. Agora, considere o método público CTable::SortData(), concebido para ser chamado (1) ao clicar nos cabeçalhos dos cabeçalhos da coluna da tabela ou (2) por meio de programação em qualquer outro momento, quando for necessário de acordo com o autor da aplicação MQL. O método CTable::SortData() deve passar o índice da coluna, a primeira coluna é ordenada por padrão. Se a coluna especificada é ordenada pela primeira vez, ou se foi ordenada por último em ordem decrescente, os valores serão ordenados em ordem crescente. Depois de ordenar os dados, a tabela é atualizada. E a última linha do método CTable::SortData() define o ícone correspondente à seta de sinal da tabela ordenada. 

class CTable : public CElement
  {
private:
   //--- Índice da coluna ordenada (WRONG_VALUE - tabela não está ordenada)
   int               m_is_sorted_column_index;
   //--- Direção da última ordenação
   ENUM_SORT_MODE    m_last_sort_direction;
   //---
public:
   //--- Organiza os dados de acordo com a coluna especificada
   void              SortData(const uint column_index=0);
  };
//+------------------------------------------------------------------+
//| Organiza os dados de acordo com a coluna especificada            |
//+------------------------------------------------------------------+
void CTable::SortData(const uint column_index=0)
  {
//--- Índice (tendo em conta a presença de cabeçalhos) para iniciar a ordenação
   uint first_index=(m_fix_first_row) ? 1 : 0;
// --- O último índice do array
   uint last_index=m_rows_total-1;
//--- A primeira vez, eles serão ordenados em ordem crescente, cada vez após isso eles serão ordenados no sentido oposto
   if(m_is_sorted_column_index==WRONG_VALUE || column_index!=m_is_sorted_column_index || m_last_sort_direction==SORT_DESCEND)
      m_last_sort_direction=SORT_ASCEND;
   else
      m_last_sort_direction=SORT_DESCEND;
//--- Armazena o índice da última coluna de dados ordenados
   m_is_sorted_column_index=(int)column_index;
//--- Ordenação
   QuickSort(first_index,last_index,column_index,m_last_sort_direction);
//--- Atualiza a tabela
   UpdateTable();
//--- Define o ícone de acordo com a direção da ordenação
   m_sort_arrow.State((m_last_sort_direction==SORT_ASCEND)? true : false);
  }

Será exigido um método para lidar com o clique nos cabeçalhos das colunas - CTable::OnClickTableHeaders(), para quando o modo de ordenação de dados for ativado. Depois de passar todos os controles para o objeto pertencente a este controle, o índice do cabeçalho (coluna) é determinado em um ciclo e, em seguida, a tabela é classificada. Imediatamente após a classificação da tabela, um evento com o novo identificador ON_SORT_DATA é gerado. Além deste identificador, a mensagem contém (1) o identificador de controle, (2) o índice da coluna ordenada e (3) o tipo de dado dessa coluna.

class CTable : public CElement
  {
private:
   //--- Manipulação do clique nos cabeçalhos da tabela
   bool              OnClickTableHeaders(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Manipulação do clique no cabeçalho da tabela                     |
//+------------------------------------------------------------------+
bool CTable::OnClickTableHeaders(const string clicked_object)
  {
//--- Sai, se o modo de ordenação está desativado
   if(!m_is_sort_mode)
      return(false);

//--- Sai, se o pressionamento não estava na célula da tabela
   if(::StringFind(clicked_object,CElement::ProgramName()+"_table_edit_",0)<0)
      return(false);
//--- Obtém o identificador do nome do objeto
   int id=CElement::IdFromObjectName(clicked_object);
//--- Sai, se o identificador não corresponder
   if(id!=CElement::Id())
      return(false);
//--- Sai, se isso não for um cabeçalho da tabela
   if(RowIndexFromObjectName(clicked_object)>0)
      return(false);
//--- Para determinar o índice da coluna
   uint column_index=0;
//--- Desloca por um índice, se o modo do cabeçalho fixo está habilitado
   int l=(m_fix_first_column) ? 1 : 0;
//--- Obtém a posição atual do controle deslizante da barra de rolagem horizontal
   int h=m_scrollh.CurrentPos()+l;
//--- Colunas
   for(uint c=l; c<m_visible_columns_total; c++)
     {
      //--- Se o pressionamento não foi nesta célula
      if(m_columns[c].m_rows[0].Name()==clicked_object)
        {
         //--- Obtém o índice da coluna
         column_index=(m_fix_first_column && c==0) ? 0 : h;
         break;
        }
      //---
      h++;
     }
//--- Ordena os dados de acordo com a coluna especificada
   SortData(column_index);
//--- Envia uma mensagem sobre isso
   ::EventChartCustom(m_chart_id,ON_SORT_DATA,CElement::Id(),m_is_sorted_column_index,::EnumToString(DataType(column_index)));
   return(true);
  }

Se o número total de colunas for maior que o número de colunas visíveis, a posição da seta de sinal da tabela ordenada deve ser ajustada ao mover a barra horizontal. Isso será feito usando o método privado CTable::ShiftSortArrow().

class CTable : public CElement
  {
private:
   //--- Desloca o sinal da seta de dados ordenados
   void              ShiftSortArrow(const uint column);
  };
//+------------------------------------------------------------------+
//| Desloca a seta para a coluna da tabela ordenada                  |
//+------------------------------------------------------------------+
void CTable::ShiftSortArrow(const uint column)
  {
//--- Exibe o objeto se o elemento não está oculto
   if(CElement::IsVisible())
      m_sort_arrow.Timeframes(OBJ_ALL_PERIODS);
//--- Calcula e define a coordenada
   int x=m_columns[column].m_rows[0].X2()-m_sort_arrow_x_gap;
   m_sort_arrow.X(x);
   m_sort_arrow.X_Distance(x);
//--- Margem da borda
   m_sort_arrow.XGap((m_anchor_right_window_side)? m_wnd.X2()-x : x-m_wnd.X());
  }

Este método será chamado de dentro do método CTable::UpdateTable(), no bloco de código em que os cabeçalhos são deslocados. Abaixo está a versão resumida do método CTable::UpdateTable() com os fragmentos adicionados. Aqui, se uma coluna ordenada foi encontrada no primeiro ciclo, a flag é definida e o sinal de seta é movido. Depois do ciclo ser finalizado, pode acontecer que a coluna ordenada exista, mas ela não foi encontrada no ciclo anterior. Isto poderia significar que ela ficou fora da área visível e deve ser escondido. Se esta for a primeira coluna (índice zero) e, ao mesmo tempo, ela é fixa, não podendo ser deslocada, o sinal da seta é ajustado a ela. 

//+------------------------------------------------------------------+
//| Atualiza os dados da tabela levando em consideração as mudanças recentes |
//+------------------------------------------------------------------+
void CTable::UpdateTable(void)
  {
//...

//--- Desloca os cabeçalhos na linha superior
   if(m_fix_first_row)
     {
      //--- Para determinar o deslocamento do ícone de ordenação
      bool is_shift_sort_arrow=false;
      //--- Colunas
      for(uint c=l; c<m_visible_columns_total; c++)
        {
         //--- Se não excedeu o intervalo do array
         if(h>=l && h<m_columns_total)
           {
            //--- Se foi encontrado a coluna ordenada
            if(!is_shift_sort_arrow && m_is_sort_mode && h==m_is_sorted_column_index)
              {
               is_shift_sort_arrow=true;
               //--- Adjust the sorting icon
               uint column=h-(h-c);
               if(column>=l && column<m_visible_columns_total)
                  ShiftSortArrow(column);
              }

            //--- Ajusta os (1) valores, (2) a cor do fundo, (3) a cor do texto e (4) o alinhamento do texto nas células
            SetCellParameters(c,0,m_vcolumns[h].m_vrows[0],m_headers_color,m_headers_text_color,m_vcolumns[h].m_text_align[0]);
           }
         //---
         h++;
        }
      //--- Se a tabela ordenada existe, mas não foi encontrada
      if(!is_shift_sort_arrow && m_is_sort_mode && m_is_sorted_column_index!=WRONG_VALUE)
        {
         //--- Esconde, se o índice for maior que zero
         if(m_is_sorted_column_index>0 || !m_fix_first_column)
            m_sort_arrow.Timeframes(OBJ_NO_PERIODS);
         //--- Define para o cabeçalho da primeira coluna
         else
            ShiftSortArrow(0);
        }

     }
//...

  }

Os arquivos com os arquivos de teste do expert pode ser baixado no final do artigo. Você pode usá-los para testar a operação por si próprio.

 

Outras atualizações da biblioteca

Como recursos adicionais, esta compilação inclui as seguintes atualizações da biblioteca:

1. Agora é possível definir a fonte e o tamanho da fonte para cada controle individualmente. Para este efeito, os campos e métodos apropriados foram adicionados a classe base de controles CElement. Por padrão, a fonte Calibri é usada, e o tamanho da fonte é de 8 pontos

class CElement
  {
protected:
   //--- Fonte
   string            m_font;
   int               m_font_size;
   //---
public:
   //--- (1) Fonte e (2) o tamanho da fonte
   void              Font(const string font)                         { m_font=font;                          }
   string            Font(void)                                const { return(m_font);                       }
   void              FontSize(const int font_size)                   { m_font_size=font_size;                }
   int               FontSize(void)                            const { return(m_font_size);                  }
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CElement::CElement(void) : m_font("Calibri"),
                           m_font_size(8)
  {
  }

Assim, os valores são retirados da classe base em todos os métodos para a criação dos controles, que exigem que a fonte seja especificada. Abaixo está um exemplo do rótulo de texto da classe CCheckBox. O mesmo foi feito em todas as classes da biblioteca. 

//+------------------------------------------------------------------+
//| Cria o rótulo de texto da caixa de seleção                       |
//+------------------------------------------------------------------+
bool CCheckBox::CreateLabel(void)
  {
//--- Formando o nome do objeto
   string name=CElement::ProgramName()+"_checkbox_lable_"+(string)CElement::Id();
//--- Coordenadas
   int x =(m_anchor_right_window_side)? m_x-m_label_x_gap : m_x+m_label_x_gap;
   int y =(m_anchor_bottom_window_side)? m_y-m_label_y_gap : m_y+m_label_y_gap;
//--- Cor do texto de acordo com o estado
   color label_color=(m_check_button_state) ? m_label_color : m_label_color_off;
//--- Define o objeto
   if(!m_label.Create(m_chart_id,name,m_subwin,x,y))
      return(false);
//--- Define as propriedades
   m_label.Description(m_label_text);
   m_label.Font(CElement::Font());
   m_label.FontSize(CElement::FontSize());

   m_label.Color(label_color);
   m_label.Corner(m_corner);
   m_label.Anchor(m_anchor);
   m_label.Selectable(false);
   m_label.Z_Order(m_zorder);
   m_label.Tooltip("\n");
//--- Margens da borda
   m_label.XGap((m_anchor_right_window_side)? x : x-m_wnd.X());
   m_label.YGap((m_anchor_bottom_window_side)? y : y-m_wnd.Y());
//--- Inicialização do array de gradiente
   CElement::InitColorArray(label_color,m_label_color_hover,m_label_color_array);
//--- Armazena o ponteiro do objeto
   CElement::AddToArray(m_label);
   return(true);
  }

 

2. Agora, as margens do ponto da borda do formulário para cada controle da interface gráfica precisam ser passados diretamente para o método de criação do controle. O cálculo será feito automaticamente. Como um exemplo, a lista abaixo mostra o método para a criação de um calendário suspenso a partir da classe personalizada CProgram

//+------------------------------------------------------------------+
//| Cria um calendário suspenso                                      |
//+------------------------------------------------------------------+
bool CProgram::CreateDropCalendar(const int x_gap,const int y_gap,const string text)
  {
//--- Passa o objeto para o painel
   m_drop_calendar.WindowPointer(m_window);
//--- Anexa para a segunda guia
   m_tabs.AddToElementsArray(1,m_drop_calendar);
//--- Define as propriedades antes da criação
   m_drop_calendar.XSize(140);
   m_drop_calendar.YSize(20);
   m_drop_calendar.AreaBackColor(clrWhite);
//--- Cria o controle
   if(!m_drop_calendar.CreateDropCalendar(m_chart_id,m_subwin,text,x_gap,y_gap))
      return(false);
//--- Adiciona o ponteiro do controle para a base
   CWndContainer::AddToElementsArray(0,m_drop_calendar);
   return(true);
  }

 


Aplicação para testar os controles

Agora, vamos criar um aplicativo MQL de teste, onde todos os novos controles podem ser testados. Crie uma interface gráfica que contém o menu principal (CMenuBar), com os menus de contexto suspensos, barra de status e duas guias. A primeira aba irá conter uma tabela do tipo CTable, com o modo de ordenação ativada. 

As três primeiras colunas da tabela terão os seguintes tipos de dados:

O restante das colunas serão padrão para o tipo TYPE_STRING. A imagem abaixo mostra a aparência da interface gráfica com a tabela na primeira guia. 

Fig. 7. Exemplo de uma tabela ordenada (ascendente) de acordo com a segunda coluna.

Fig. 7. Exemplo de uma tabela ordenada (ascendente) de acordo com a segunda coluna.


Crie quatro controles na segunda guia: 

  • Calendário suspenso (a classe CDropCalendar).
  • O controle Horário (a classe CTimeEdit).
  • Lista de caixas de seleção (a classe CCheckBoxList).
  • Lista (a classe CListView).

A imagem abaixo mostra a aparência na interface gráfica da aplicação MQL de teste:

Fig. 8. Controles da segunda guia.

Fig. 8. Controles da segunda guia.


O código fonte da aplicação de teste é fornecida no fim do artigo. 

 

Conclusão

A esquemática da biblioteca para a criação das interfaces gráficas no atual estágio de desenvolvimento é parecido com a imagem abaixo:

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

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


Este não é o último artigo da série sobre interfaces gráficas. Nós vamos continuar a melhorá-la, completando com novos recursos. Abaixo, você pode baixar a versão mais recente da biblioteca e seus arquivos de teste.

Se você tiver dúvidas sobre a utilização do material a partir desses arquivos, você poderá consultar a descrição detalhada do desenvolvimento da biblioteca em um dos artigos da lista abaixo ou fazer sua pergunta nos comentários deste artigo.

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

Arquivos anexados |
Modelagem 3D em MQL5 Modelagem 3D em MQL5

As séries temporais são um sistema dinâmico em que os valores de uma variável aleatória chegam de forma consistente, isto é, de forma contínua ou em intervalos. A transição para a análise 3D de mercado fornece um novo olhar sobre os complexos processos e fenômenos de interesse para os investigadores. Este artigo descreve as funções de visualização para representações em 3D de dados bidimensionais.

Detecção automática de pontos extremos com base numa variação de preço especificado Detecção automática de pontos extremos com base numa variação de preço especificado

A automação com estratégias de negociação que envolvem padrões gráficos, requer a capacidade de procurar pontos extremos nos gráficos para processamento e interpretação. As ferramentas existentes nem sempre fornecem essa capacidade. Os algoritmos descritos no artigo permitem encontrar todos os pontos extremos nos gráficos. As ferramentas discutidas aqui são igualmente eficientes durante os movimentos de tendência e de lateralidade. Os resultados obtidos não são fortemente afetados por um período de tempo selecionado e são apenas definidos por uma escala especifica.

Interfaces Gráficas X: Gestão avançada de listas e tabelas. Otimização do código (build 7) Interfaces Gráficas X: Gestão avançada de listas e tabelas. Otimização do código (build 7)

O código da biblioteca precisa ser otimizado: ele deve ser mais regularizado, o que é - mais legível e compreensível para estudar. Além disso, nós vamos continuar a desenvolver os controles criados anteriormente: listas, tabelas e barras de rolagem.

Canal universal com GUI Canal universal com GUI

Todos os indicadores de canais apresentam três linhas, isto é: central, superior e inferior. A linha central, quanto à sua plotagem, é idêntica à média móvel. Na maioria dos casos, para a plotagem do canal, é utilizada a média móvel. As linhas superior e inferior são equidistantes da linha central. Esta distância pode ser determinada simplesmente em pontos, em porcentagem do preço (indicador Envelopes), pode ser usado o valor do desvio padrão (bandas de Bollinger), pode ser empregado o valor do indicador de ATR (canal Keltner).