Interfaces Gráficas VIII: O Controle Lista Hierárquica (Capítulo 2)
Conteúdo
- Introdução
- O controle lista hierárquica
- Desenvolvimento da classe CTreeItem para a criação do elemento da lista hierárquica
- A classe CPointer usada para criar um ponteiro do cursor do mouse
- Desenvolvimento da classe CTreeView usado para a criação de uma lista hierárquica
- Parâmetros para a formação das listas do controle
- Métodos para gerenciar as listas do controle
- Gerenciando a largura das áreas da lista
- Modo de elementos guia
- Métodos para o tratamento de eventos
- Integração do controle ao motor da biblioteca
- Teste do controle lista hierárquica
- Conclusão
Introdução
Informações mais detalhadas sobre a finalidade desta biblioteca está disponível a partir do primeiro artigo - Interfaces gráficas I: Preparação da Estrutura da Biblioteca (Capítulo 1). A lista completa dos links para os artigos se encontra no final de cada capítulo da série. 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.
O capítulo anterior da parte VIII da série Interfaces Gráficas, nós focamos sobre os elementos do calendário estático e suspenso. O segundo capítulo será dedicado a um elemento igualmente complexo - uma lista hierárquica, que está incluída em cada biblioteca multifuncional, usada para a criação de interfaces gráficas. A lista hierárquica implementada neste artigo contém várias configurações flexíveis e modos, permitindo assim ajustar este elemento de controle às suas necessidades.
A implementação de uma outra classe útil, usada na criação dos ponteiros do cursor do mouse, também será abordada neste artigo. Além disso, nós também vamos dar um exemplo do uso deste tipo de controle em uma lista hierárquica.
O controle lista hierárquica
Ao contrário das listas simples, a lista hierárquica permite organizar seus elementos por categorias, sem limite de ramificações. Cada elemento (item ou nó) que contém uma lista de outros elementos (1 ou mais) está equipado com um controle que permite recolher e expandir uma lista local. Além da descrição do texto, cada elemento pode ter um rótulo (pictograma) para simplificar a experiência dos usuários. Os pontos finais (que não têm listas locais) podem conter grupos de controles para exibir uma aplicação em uma interface gráfica ou executar uma determinada função.
A lista hierárquica pode ser usada para a criação de catálogos com elementos que possuem uma hierarquia estabelecida. Por exemplo, ela pode ser usada para criar um navegador de arquivos semelhante à forma como é feita no Windows Explorer. O MetaTrader e o MetaEditor também possuem janelas de navegação onde é aplicado uma lista hierárquica.
No MetaTrader, você pode exibir/ocultar a janela "Navegador" (veja a imagem abaixo), pressionando as teclas ‘Ctrl+N’:
Fig. 1. Janela "Navegador" no MetaTrader.
No MetaEditor você pode exibir/ocultar a janela "Navegador" (veja a imagem abaixo), pressionando as teclas ‘Ctrl+D’:
Fig. 2. Janela "Navegador" no MetaTrader.
Desenvolvimento da classe CTreeItem para a criação do elemento da lista hierárquica
Antes de continuar a escrever a classe lista hierárquica, mais dois controles terão de ser criados. A lista hierárquica é o controle mais importante. Sua estrutura se assemelha com o controle menu de contexto (CMenuItem) que analisamos no artigo Interfaces Gráficas II: O Elemento de Menu (Capítulo 1), no entanto, ele possui propriedades únicas e, por conseguinte, requer uma classe separada para ser criada.
O segundo elemento é o ponteiro do mouse. Ele será usado como um indicador para avaliar o estado de prontidão da alteração da largura da área da lista hierárquica. A fim de criar este tipo de elemento, nós vamos escrever uma classe separada chamada de CPointer que poderia ser utilizada em outros elementos mais tarde. A classe CPointer será considerada na seção seguinte.
Desde o início, nós precisamos ter certeza de quais objetos serão recolhidos pelo controle lista hierárquica.
Os componentes do controle lista hierárquica estão listados abaixo.
- Fundo
- Sinal da lista local de elementos. Setas e pictogramas de mais/menos são usados para exibir o estado (expandido/recolhido).
- Rótulo do elemento. Por exemplo, pode ser necessário classificar visualmente um elemento em uma determinada categoria.
- Descrição textual do elemento
Fig. 3. Componentes do elemento da lista hierárquica
Nós criamos um arquivo com o nome TreeItem.mqh e incluímos ele à biblioteca (WndContainer.mqh):
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "TreeItem.mqh"
No arquivo TreeItem.mqh nós precisamos criar a classe CTreeItem com os métodos padrão para todos os elementos da biblioteca (veja o código abaixo):
//+------------------------------------------------------------------+ //| TreeItem.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" //+------------------------------------------------------------------+ //| Classe para criar o elemento da lista hierárquica | //+------------------------------------------------------------------+ class CTreeItem : public CElement { private: //--- Ponteiro para o formulário ao qual o elemento está anexado CWindow *m_wnd; //--- public: CTreeItem (void); ~CTreeItem (void); //--- Armazena o ponteiro ao formulário void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } //--- public: //--- Manipulador de eventos do gráfico virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- Timer virtual void OnEventTimer(void) {} //--- Move o elemento virtual void Moving(const int x,const int y); //--- (1) Exibe, (2) oculta, (3) reseta, (4) remove virtual void Show(void); virtual void Hide(void); virtual void Reset(void); virtual void Delete(void); //--- (1) Definir (2), resetar as prioridades para o clique esquerdo do mouse virtual void SetZorders(void); virtual void ResetZorders(void); //--- Resetar a cor virtual void ResetColors(void); };
Antes da criação do controle, estarão disponíveis as seguintes propriedades para ajustar sua aparência:
- As cores de fundo do elemento em vários estados
- Rótulo do elemento
- Ícones com seta indicam o estado atual (expandido/recolhido) da lista local de elementos
- Deslocamentos do rótulo de texto
- Cores de texto em diferentes estados
class CTreeItem : public CElement { private: //--- Cores de fundo em diferentes estados color m_item_back_color; color m_item_back_color_hover; color m_item_back_color_selected; //--- Ícones para o elemento seta string m_item_arrow_file_on; string m_item_arrow_file_off; string m_item_arrow_selected_file_on; string m_item_arrow_selected_file_off; //--- Rótulo do elmento string m_icon_file; //--- Deslocamentos do rótulo de texto int m_label_x_gap; int m_label_y_gap; //--- Cores de texto em diferentes estados do elemento color m_item_text_color; color m_item_text_color_hover; color m_item_text_color_selected; //--- public: //--- Cores do elemento de fundo void ItemBackColor(const color clr) { m_item_back_color=clr; } void ItemBackColorHover(const color clr) { m_item_back_color_hover=clr; } void ItemBackColorSelected(const color clr) { m_item_back_color_selected=clr; } //--- (1) Define o rótulo de elemento, (2) define os ícones para o elemento seta void IconFile(const string file_path) { m_icon_file=file_path; } void ItemArrowFileOn(const string file_path) { m_item_arrow_file_on=file_path; } void ItemArrowFileOff(const string file_path) { m_item_arrow_file_off=file_path; } void ItemArrowSelectedFileOn(const string file_path) { m_item_arrow_selected_file_on=file_path; } void ItemArrowSelectedFileOff(const string file_path) { m_item_arrow_selected_file_off=file_path; } //--- (1) Retorna o elemento texto, (2) define os deslocamentos para o rótulo de texto string LabelText(void) const { return(m_label.Description()); } void LabelXGap(const int x_gap) { m_label_x_gap=x_gap; } void LabelYGap(const int y_gap) { m_label_y_gap=y_gap; } //--- As cores de texto em diferentes estados void ItemTextColor(const color clr) { m_item_text_color=clr; } void ItemTextColorHover(const color clr) { m_item_text_color_hover=clr; } void ItemTextColorSelected(const color clr) { m_item_text_color_selected=clr; } };
Nós vamos precisar de 4 métodos privados e 1 público, a fim de criar uma lista do tipo árvore (hierárquica). Por favor note que um objeto gráfico parecido com a CEdit será usada como rótulo de texto. Em breve, nós vamos especificar o que será necessário para essa finalidade.
class CTreeItem : public CElement { private: //--- Objetos para criar uma vista do tipo árvore CRectLabel m_area; CBmpLabel m_arrow; CBmpLabel m_icon; CEdit m_label; //--- public: //--- Métodos para a criação do elemento do tipo árvore bool CreateTreeItem(const long chart_id,const int subwin,const int x,const int y,const ENUM_TYPE_TREE_ITEM type, const int list_index,const int node_level,const string item_text,const bool item_state); //--- private: bool CreateArea(void); bool CreateArrow(void); bool CreateIcon(void); bool CreateLabel(void); };
Os valores para os parâmetros usados para posicionar um elemento na lista em relação a outros elementos devem ser enviados para o método público CTreeItem::CreateTreeItem(). Alguns deles irão participar na formação do nome dos objetos gráficos utilizados na coleta do elemento. Os campos relevantes da classe são inicializados com os valores dos parâmetros enviados, que são utilizados nos métodos particulares para a criação dos objetos do elemento.
As características-chave estão listadas abaixo.
- O tipo do elemento pode ser selecionado a partir de duas opções: (1) um elemento simples (TI_SIMPLE) que não contém outros elementos e é um nó final, ou (2) aquele que tem os elementos (TI_HAS_ITEMS). Portanto, a enumeração ENUM_TYPE_TREE_ITEM é adicionada ao Enums.mqh:
//+------------------------------------------------------------------+ //| Enumeração dos tipos de elemento da lista hierárquica | //+------------------------------------------------------------------+ enum ENUM_TYPE_TREE_ITEM { TI_SIMPLE =0, TI_HAS_ITEMS =1 };
- Índice geral na lista.
- Nível do nó (número).
- Texto exibido (descrição).
- Estado do elemento.
Por favor, preste atenção como a seta de deslocamento é calculada (sinal de uma lista local em um elemento). Eventualmente, cada nó de uma árvore é movido a partir do nó anterior para o valor calculado nessa string.
class CTreeItem : public CElement { private: //--- Margem pela seta (sinal) int m_arrow_x_offset; //--- Tipo do elemento ENUM_TYPE_TREE_ITEM m_item_type; //--- Índice do elemento em uma lista geral int m_list_index; //--- Nível do nó int m_node_level; //--- Texto exibido no elemento string m_item_text; //--- Estado da lista de elementos(expandido/recolhido) bool m_item_state; }; //+------------------------------------------------------------------+ //| Criar um elemento do tipo árvore | //+------------------------------------------------------------------+ bool CTreeItem::CreateTreeItem(const long chart_id,const int subwin,const int x,const int y,const ENUM_TYPE_TREE_ITEM type, const int list_index,const int node_level,const string item_text,const bool item_state) { //--- Sai, se não houver um ponteiro para o formulário if(::CheckPointer(m_wnd)==POINTER_INVALID) { ::Print(__FUNCTION__," > Antes de criar o elemento da lista, a classe deve ser passada. " "para o ponteiro do formulário CTreeItem::WindowPointer(CWindow &object)"); return(false); } //--- Inicialização das variáveis m_id =m_wnd.LastId()+1; m_chart_id =chart_id; m_subwin =subwin; m_x =x; m_y =y; m_item_type =type; m_list_index =list_index; m_node_level =node_level; m_item_text =item_text; m_item_state =item_state; m_arrow_x_offset =(m_node_level>0)? (12*m_node_level)+5 : 5; //--- Deslocamentos do ponto extremo CElement::XGap(CElement::X()-m_wnd.X()); CElement::YGap(CElement::Y()-m_wnd.Y()); //--- Criando o elemento de menu if(!CreateArea()) return(false); if(!CreateArrow()) return(false); if(!CreateIcon()) return(false); if(!CreateLabel()) return(false); //--- Oculta o controle para uma janela de diálogo ou de uma janela minimizada if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized()) Hide(); //--- return(true); }
Os seguintes componentes serão utilizados para formar o nome dos objetos do elemento (ver o código abaixo):
- Nome do programa
- Índice do elemento
- Sinal de pertinência ao elemento («TreeItem»)
- Sinal de pertinência à parte do elemento
- Índice geral do elemento do tipo árvore
- Identificador do elemento
Nós usaremos um método particular para a criação dos objetos que compõem o elemento CTreeItem::CreateArrow() como um exemplo. A biblioteca, por padrão, tem ícones para uma lista suspensa. Você pode baixar eles nos links que se encontram no final deste artigo. Eles podem ser predeterminados antes de criar um elemento, se necessário.
O deslocamento do lado esquerdo do elemento para este objeto é calculado com o parâmetro m_node_level (nível do nó) no método principal da criação do elemento antes de criar os objetos do elemento. Se for verificado que este é um tipo de elemento simples (TI_SIMPLE), então, o programa sai do método. Por favor note que as coordenadas para este objeto devem ser salvas para verificar o tipo do elemento (antes do possível encerramento do método), uma vez que eles serão utilizados para calcular as coordenadas seguintes numa fila de criação dos objetos de elemento. O mesmo princípio é aplicado no método CTreeItem::CreateIcon() para a criação do rótulo de elemento.
Aqui, logo após ele ter sido criado, o objeto tem o estado estabelecido, que é enviado para o método principal pelo valor item_state, e pode ser usado para controlar quais itens devem ser abertos após a lista hierárquica ser criada.
//+------------------------------------------------------------------+ //| Cria uma seta (sinal de uma lista suspensa) | //+------------------------------------------------------------------+ #resource "\\Images\\EasyAndFastGUI\\Controls\\RightTransp_black.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\RightTransp_white.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\RightTransp_rotate_black.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\RightTransp_rotate_white.bmp" //--- bool CTreeItem::CreateArrow(void) { //--- Calcula as coordenadas int x =CElement::X()+m_arrow_x_offset; int y =CElement::Y()+2; //--- Salva as coordenadas para calcular as coordenadas que seguirão em uma fila para criar os objetos do elemento m_arrow.X(x); m_arrow.Y(y); //--- Sai se um ponto não tem nenhuma lista suspensa if(m_item_type!=TI_HAS_ITEMS) return(true); //--- Formação do nome do objeto string name=CElement::ProgramName()+"_"+(string)CElement::Index()+"_treeitem_arrow_"+(string)m_list_index+"__"+(string)CElement::Id(); //--- Ajusta os ícones por padrão if(m_item_arrow_file_on=="") m_item_arrow_file_on="Images\\EasyAndFastGUI\\Controls\\RightTransp_rotate_black.bmp"; if(m_item_arrow_file_off=="") m_item_arrow_file_off="Images\\EasyAndFastGUI\\Controls\\RightTransp_black.bmp"; if(m_item_arrow_selected_file_on=="") m_item_arrow_selected_file_on="Images\\EasyAndFastGUI\\Controls\\RightTransp_rotate_white.bmp"; if(m_item_arrow_selected_file_off=="") m_item_arrow_selected_file_off="Images\\EasyAndFastGUI\\Controls\\RightTransp_white.bmp"; //--- Define o objeto if(!m_arrow.Create(m_chart_id,name,m_subwin,x,y)) return(false); //--- Define as propriedades m_arrow.BmpFileOn("::"+m_item_arrow_file_on); m_arrow.BmpFileOff("::"+m_item_arrow_file_off); m_arrow.State(m_item_state); m_arrow.Corner(m_corner); m_arrow.GetInteger(OBJPROP_ANCHOR,m_anchor); m_arrow.Selectable(false); m_arrow.Z_Order(m_arrow_zorder); m_arrow.Tooltip("\n"); //--- Deslocamentos do ponto extremo m_arrow.XGap(x-m_wnd.X()); m_arrow.YGap(y-m_wnd.Y()); //--- Armazena o ponteiro de objeto CElement::AddToArray(m_arrow); return(true); }
Nós vamos exigir os métodos para gerenciar os tamanhos (largura) e a cor da lista hierárquica depois que ela foi criada. A criação de uma lista hierárquica é implementada de tal maneira que, além da área com a lista hierárquica, ela tem a oportunidade de ativar o modo que exibe a área com o conteúdo de itens selecionados para à direita de uma árvore. Em outras palavras, haverá uma lista de itens contidos em um item selecionado de uma lista hierárquica nesta área. Quando você passa o mouse sobre a fronteira entre estas áreas e segura o botão esquerdo do mouse pressionado, você também pode mudar (ao mesmo tempo) a largura das áreas da lista hierárquica e a lista de conteúdo.
Ao mudar a largura das áreas (1) as coordenadas X dos objetos dos elementos da lista de conteúdo e (2) a largura dos objetos dos elementos de ambas as listas serão atualizadas. Portanto, para exibir o texto dos objetos, é utilizado o objeto do tipo CEdit em vez da CLabel. Se você usar os objetos do tipo CLabel, então, ao alterar os tamanhos das áreas da lista, você pode enfrentar um problema quando os rótulos de texto vão além das margens do formulário (janela).
A fim de atualizar a coordenada X, nós escrevemos o método CTreeItem::UpdateX(). A fim de definir a coordenada X, ela deve ser enviada para este método. Neste caso, todos os cálculos serão realizados na classe da lista hierárquica. Voltaremos neste assunto em breve.
class CTreeItem : public CElement { public: void UpdateX(const int x); }; //+------------------------------------------------------------------+ //| Atualiza a coordenada X | //+------------------------------------------------------------------+ void CTreeItem::UpdateX(const int x) { //--- Atualiza as coordenadas comuns e os deslocamentos a partir do ponto extremo CElement::X(x); CElement::XGap(CElement::X()-m_wnd.X()); //--- Coordenadas e a deslocamento do plano de fundo m_area.X_Distance(CElement::X()); m_area.XGap(CElement::X()-m_wnd.X()); //--- Coordenadas e o deslocamento da seta int l_x=CElement::X()+m_arrow_x_offset; m_arrow.X(l_x); m_arrow.X_Distance(l_x); m_arrow.XGap(l_x-m_wnd.X()); //--- Coordenadas e o deslocamento do ícone l_x=m_arrow.X()+17; m_icon.X(l_x); m_icon.X_Distance(l_x); m_icon.XGap(l_x-m_wnd.X()); //--- Coordenadas e o deslocamento do rótulo de texto l_x=(m_icon_file=="")? m_icon.X() : m_icon.X()+m_label_x_gap; m_label.X(l_x); m_label.X_Distance(l_x); m_label.XGap(l_x-m_wnd.X()); }
A fim de alterar a largura do elemento da lista, o método CTreeItem::UpdateWidth() deve ser usado:
class CTreeItem : public CElement { public: void UpdateWidth(const int width); }; //+------------------------------------------------------------------+ //| Atualiza a largura | //+------------------------------------------------------------------+ void CTreeItem::UpdateWidth(const int width) { //--- Largura do fundo CElement::XSize(width); m_area.XSize(width); m_area.X_Size(width); //--- Largura do rótulo de texto int w=CElement::X2()-m_label.X()-1; m_label.XSize(w); m_label.X_Size(w); }
Além da atualização da coordenada X e da largura dos elementos, será necessário ter um método para atualizar a coordenada Y. A implementação de ambas as listas do elemento supõe que durante a execução do elemento CTreeView, todos os elementos da lista são criados imediatamente, em vez de apenas exibir o número, como é feito nos tipos CListView, CLabelsTable e CTable, discutidos anteriormente nesta série de artigos. Esta tecnologia será aplicada quando os elementos são ocultos por não se encaixarem na área da lista. Em vez de uma atualização regular de vários parâmetros de objetos, ao rolar a lista para baixo ou retrair/expandir uma lista local de elementos, apenas a visibilidade do elemento será gerido aqui. Quando os elementos que não são necessários no momento puderem ser ocultados, então a coordenada Y deve ser atualizada no momento da exibição. Para isso, nós precisamos escrever o método CTreeItem::UpdateY(), o código é mostrado abaixo.
class CTreeItem : public CElement { public: void UpdateY(const int y); }; //+------------------------------------------------------------------+ //| Atualiza a coordenada Y | //+------------------------------------------------------------------+ void CTreeItem::UpdateY(const int y) { //--- Atualiza as coordenadas comuns e os deslocamentos a partir do ponto extremo CElement::Y(y); CElement::YGap(CElement::Y()-m_wnd.Y()); //--- Coordenadas e a deslocamento do plano de fundo m_area.Y_Distance(CElement::Y()); m_area.YGap(CElement::Y()-m_wnd.Y()); //--- Coordenadas e o deslocamento da seta int l_y=CElement::Y()+2; m_arrow.Y(l_y); m_arrow.Y_Distance(l_y); m_arrow.YGap(l_y-m_wnd.Y()); //--- Coordenadas e o deslocamento do ícone l_y=CElement::Y()+2; m_icon.Y(l_y); m_icon.Y_Distance(l_y); m_icon.YGap(l_y-m_wnd.Y()); //--- Coordenadas e o deslocamento do rótulo de texto l_y=CElement::Y()+m_label_y_gap; m_label.Y(l_y); m_label.Y_Distance(l_y); m_label.YGap(l_y-m_wnd.Y()); }
A gestão da cor do elemento será realizada na classe da lista hierárquica (CTreeView), e serão necessários os seguintes métodos:
- para alterar a cor do elemento sobre o estado indicado;
- para alterar a cor do objeto quando o mouse estiver sobre ele.
Os códigos destes métodos são apresentados abaixo:
class CTreeItem : public CElement { public: //--- Altera a cor do elemento, dependendo do estado em destaque void HighlightItemState(const bool state); //--- Altera a cor quando o mouse estiver sobre ele void ChangeObjectsColor(void); }; //+------------------------------------------------------------------+ //| Altera a cor do elemento, dependendo do estado em destaque | //+------------------------------------------------------------------+ void CTreeItem::HighlightItemState(const bool state) { m_area.BackColor((state)? m_item_back_color_selected : m_item_back_color); m_label.BackColor((state)? m_item_back_color_selected : m_item_back_color); m_label.BorderColor((state)? m_item_back_color_selected : m_item_back_color); m_label.Color((state)? m_item_text_color_selected : m_item_text_color); m_arrow.BmpFileOn((state)? "::"+m_item_arrow_selected_file_on : "::"+m_item_arrow_file_on); m_arrow.BmpFileOff((state)? "::"+m_item_arrow_selected_file_off : "::"+m_item_arrow_file_off); } //+------------------------------------------------------------------+ //| Altera a cor quando o mouse estiver sobre ele | //+------------------------------------------------------------------+ void CTreeItem::ChangeObjectsColor(void) { if(CElement::MouseFocus()) { m_area.BackColor(m_item_back_color_hover); m_label.BackColor(m_item_back_color_hover); m_label.BorderColor(m_item_back_color_hover); m_label.Color(m_item_text_color_hover); } else { m_area.BackColor(m_item_back_color); m_label.BackColor(m_item_back_color); m_label.BorderColor(m_item_back_color); m_label.Color(m_item_text_color); } }
Além disso, nós vamos considerar a classe que visa a criação de indicadores para o cursor do mouse em uma interface gráfica.
A classe CPointer usada para criar um ponteiro do cursor do mouse
A lista hierárquica consiste de duas áreas. Uma lista hierárquica está posicionada na área à esquerda. No lado direito da lista hierárquica há o conteúdo do elemento em destaque. Mais detalhes serão fornecidos abaixo, e agora nós temos de pensar no fato de que o tamanho (largura) dessas áreas podem ser alterados, já que isso é feito, por exemplo, no Windows Explorer. Quando o mouse estiver sobre a margem, onde as áreas da lista hierárquica e a de conteúdo estão ligadas, o seu ponteiro é alterado para a seta de dois lados. Vamos criar a classe CPointer com uma funcionalidade que permita gerir os ponteiros para o cursor do mouse na biblioteca em desenvolvimento. Até o momento, o cursor do sistema do mouse não pode ser substituído via linguagem MQL, mas nós podemos adicionar um ícone de usuário para ele, fazendo uma interface gráfica mais amigável.
A classe CPointer, assim como os elementos restantes da biblioteca, será herdada da classe CElement. Você só pode se conectar à esses elementos onde eles serão usados. Ele não requer um ponteiro para o formulário já que este tipo de objetos não está ligado a nada. Ele não precisa ser inserido na base de elementos, e o controle sobre este elemento será inteiramente na classe que ele está conectado.
Nós criarmos, no mesmo diretório onde os arquivos e bibliotecas estão localizadas, o arquivo Pointer.mqh com a classe CPointer. A fim de criar um ponteiro, nós usamos o objeto do tipo CBmpLabel. A classe CBmpLabel está localizada no arquivo Objects.mqh, cujo conteúdo foi considerado na primeira parte destes artigos.
//+------------------------------------------------------------------+ //| Pointer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" //+------------------------------------------------------------------+ //| Classe para criar um ponteiro do cursor do mouse | //+------------------------------------------------------------------+ class CPointer : public CElement { private: //--- Objetos para criar o elemento CBmpLabel m_pointer_bmp; /--- public: CPointer(void); ~CPointer(void); //--- public: //--- Move o elemento virtual void Moving(const int x,const int y); //--- (1) Exibe, (2) oculta, (3) reseta, (4) remove virtual void Show(void); virtual void Hide(void); virtual void Reset(void); virtual void Delete(void); }; //+------------------------------------------------------------------+ //| Construtor | //+------------------------------------------------------------------+ CPointer::CPointer(void) { } //+------------------------------------------------------------------+ //| Destrutor | //+------------------------------------------------------------------+ CPointer::~CPointer(void) { }
Na versão atual da biblioteca, nós podemos definir um de quatro tipos de ponteiros para o cursor do mouse. Por conveniência de uso, nós adicionamos a enumeração ENUM_MOUSE_POINTER para o arquivo Enums.mqh (veja o código abaixo). Além de quatro tipos pré-definidos, nós poderíamos escolher um tipo personalizado (MP_CUSTOM).
//+------------------------------------------------------------------+ //| 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 };
Descrição das enumerações listadas acima:
- MP_CUSTOM — tipo personalizado.
- MP_X_RESIZE — altera os tamanhos horizontais.
- MP_Y_RESIZE — altera os tamanhos verticais.
- MP_XY1_RESIZE — altera os tamanhos na diagonal 1.
- MP_XY2_RESIZE — altera os tamanhos na diagonal 2.
Antes de criar um ponteiro do cursor do mouse, é necessário definir o seu tipo, e os ícones correspondentes serão definidos automaticamente em um objeto gráfico a partir do conjunto pré-especificado nos recursos do elemento. O arquivo com esses ícones podem ser baixados para o seu PC, por favor, procure-o em anexo no final deste artigo.
//--- Recursos #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_resize.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_resize_blue.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_y_resize.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_y_resize_blue.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_xy1_resize.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_xy1_resize_blue.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_xy2_resize.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_xy2_resize_blue.bmp"
Se você deseja definir seus ícones para um ponteiro, então, selecione o tipo MP_CUSTOM a partir da enumeração ENUM_MOUSE_POINTER e utilize os métodos CPointer::FileOn() e CPointer::FileOff() para indicar o caminho dos ícones.
class CPointer : public CElement { private: //--- Ícones para o ponteiro string m_file_on; string m_file_off; //--- Tipo de um ponteiro ENUM_MOUSE_POINTER m_type; //--- public: //--- Define os rótulos para um ponteiro void FileOn(const string file_path) { m_file_on=file_path; } void FileOff(const string file_path) { m_file_off=file_path; } //--- Retorna e define o tipo de um ponteiro ENUM_MOUSE_POINTER Type(void) const { return(m_type); } void Type(ENUM_MOUSE_POINTER type) { m_type=type; } };
Além disso, os métodos para atualizar as coordenadas e retornar/definir os estados do ponteiro serão necessários aqui:
class CPointer : public CElement { public: //--- Retorna e define o estado de um ponteiro bool State(void) const { return(m_pointer_bmp.State()); } void State(const bool state) { m_pointer_bmp.State(state); } //--- Atualiza as coordenadas void UpdateX(const int x) { m_pointer_bmp.X_Distance(x); } void UpdateY(const int y) { m_pointer_bmp.Y_Distance(y); } };
O construtor da classe para o ponteiro tem, por padrão, o tipo MP_X_RESIZE:
//+------------------------------------------------------------------+ //| Construtor | //+------------------------------------------------------------------+ CPointer::CPointer(void) : m_file_on(""), m_file_off(""), m_type(MP_X_RESIZE) { }
Nós precisamos de um método para que antes da criação do controle, será definido o ícone para o ponteiro, dependendo do tipo estabelecido. Para isso nós vamos escrever o método CPointer::SetPointerBmp(). Se acontecer do usuário selecionar o tipo (MP_CUSTOM) e não for especificado os caminhos para os ícones, então, será exibido uma mensagem relevante no registro.
class CPointer : public CElement { private: //--- Define os ícones para o ponteiro do cursor do mouse void SetPointerBmp(void); }; //+------------------------------------------------------------------+ //| Define os ícones para o ponteiro baseado no tipo do ponteiro | //+------------------------------------------------------------------+ void CPointer::SetPointerBmp(void) { switch(m_type) { case MP_X_RESIZE : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_x_resize_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_x_resize.bmp"; break; case MP_Y_RESIZE : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_y_resize_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_y_resize.bmp"; break; case MP_XY1_RESIZE : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_xy1_resize_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_xy1_resize.bmp"; break; case MP_XY2_RESIZE : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_xy2_resize_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_xy2_resize.bmp"; break; } //--- Se o tipo personalizado (MP_CUSTOM) for especificado if(m_file_on=="" || m_file_off=="") ::Print(__FUNCTION__," > Ambos os ícones devem ser definidos para o ponteiro do cursor!"); }
We need one public method CPointer::CreatePointer() to create an element, its code is shown in the listing below. O índice do controle irá participar na formação do nome do objeto gráfico, seu valor é definido no construtor da classe CPointer e é igual a zero por padrão. Isso é necessário para que haja uma oportunidade de criar vários objetos do tipo CPointer para várias finalidades dentro do mesmo controle na qual o cursor estará ligado.
class CPointer : public CElement { public: //--- Cria o rótulo do ponteiro bool CreatePointer(const long chart_id,const int subwin); }; //+------------------------------------------------------------------+ //| Cria o ponteiro | //+------------------------------------------------------------------+ bool CPointer::CreatePointer(const long chart_id,const int subwin) { //--- Formação do nome do objeto string name=CElement::ProgramName()+"_pointer_bmp_"+(string)CElement::Index()+"__"+(string)CElement::Id(); //--- Define os ícones do indicador SetPointerBmp(); //--- Cria o objeto if(!m_pointer_bmp.Create(m_chart_id,name,m_subwin,0,0)) return(false); //--- Define as propriedades m_pointer_bmp.BmpFileOn("::"+m_file_on); m_pointer_bmp.BmpFileOff("::"+m_file_off); m_pointer_bmp.Corner(m_corner); m_pointer_bmp.Selectable(false); m_pointer_bmp.Z_Order(0); m_pointer_bmp.Tooltip("\n"); //--- Oculta o objeto m_pointer_bmp.Timeframes(OBJ_NO_PERIODS); return(true); }
Agora, nós temos todos os elementos necessários para a criação da lista hierárquica, nós podemos prosseguir com o estudo da classe CTreeView antes de tentar criá-la.
Desenvolvimento da classe CTreeView usado para a criação de uma lista hierárquica
Nós criamos o arquivo TreeView.mqh com a classe CTreeView com os métodos padrão, como fizemos com todos os controles da biblioteca, e incluímos ele no arquivo WndContainer.mqh:
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "TreeView.mqh"
Vamos listar os componentes do controle que formará a lista hierárquica.
- Plano de fundo da lisa hierárquica
- Lista dos elementos da lista hierárquica
- Barra de rolagem vertical da lista hierárquica
- Plano de fundo de uma lista de conteúdo
- Lista de elementos da lista de conteúdo
- Barra de rolagem horizontal de uma lista de conteúdo
- Ponteiro para o cursor do mouse para controlar como é alterado a largura da área da lista hierárquica e de conteúdo
Fig. 4. Componentes da lista hierárquica.
O ícone do ponteiro do cursor do mouse será oculto após sua criação. Ele só irá aparecer na área estreita da borda que ligam as áreas das listas quando o cursor do mouse estiver sobre ela.
A fim de criar o controle lista hierárquica, serão necessários sete métodos privados e um público:
class CTreeView : public CElement { private: //--- Objetos para criar o controle CRectLabel m_area; CRectLabel m_content_area; CTreeItem m_items[]; CTreeItem m_content_items[]; CScrollV m_scrollv; CScrollV m_content_scrollv; CPointer m_x_resize; //--- public: //--- Métodos para criar a lista hierárquica bool CreateTreeView(const long chart_id,const int subwin,const int x,const int y); //--- private: bool CreateArea(void); bool CreateContentArea(void); bool CreateItems(void); bool CreateScrollV(void); bool CreateContentItems(void); bool CreateContentScrollV(void); bool CreateXResizePointer(void); };
Como exemplo, nós vamos fornecer o código de apenas um deles - o método CTreeView::CreateXResizePointer() para a criação de um ponteiro para o cursor do mouse (ver o código abaixo). Nós precisamos prestar atenção aos seguintes detalhes:
- Se uma das seguintes condições forem atendidas, o ponteiro para o cursor não será criado:
- o modo que permite alterar a largura da listas está desativado.
- o modo de elementos guia está ativado.
- Os deslocamentos do ponteiro serão calculados a partir do sistema do cursor do mouse (este tipo de elemento não está ligado à janela como os outros elementos).
- Se vários ponteiros são necessários no elemento, então, cada um deles precisará ter o seu próprio conjunto de índices do elemento. Como apenas um ponteiro será usado na versão atual da lista hierárquica, nós podemos omitir esta propriedade, uma vez que ela é inicializada, por padrão, com o valor 0 no construtor do ponteiro.
- É necessário indicar o identificador do elemento que o ponteiro está ligado. Isso é necessário para evitar conflitos entre os nomes dos objetos gráficos dos vários elementos, que podem ser utilizados pelos ponteiros do cursor.
//+------------------------------------------------------------------+ //| Cria o ponteiro para o cursor na mudança da largura | //+------------------------------------------------------------------+ bool CTreeView::CreateXResizePointer(void) { //--- Sai se não precisar alterar a largura da área do conteúdo ou // o modo de elementos guia está habilitado if(!m_resize_content_area_mode || m_tab_items_mode) return(true); //--- Define as propriedades m_x_resize.XGap(12); m_x_resize.YGap(9); m_x_resize.Id(CElement::Id()); m_x_resize.Type(MP_X_RESIZE); //--- Cria o elemento if(!m_x_resize.CreatePointer(m_chart_id,m_subwin)) return(false); //--- return(true); }
Antes de descrever outros métodos da classe CTreeView, vamos ver qual funcionalidade é necessária no controle lista hierárquica. Nós implementamos a classe para a criação deste controle de maneira que, eventualmente, nós podemos utilizar as listas hierárquicas para diversos fins.
Por exemplo, você deve ter em mente sobre algumas particularidades no desenvolvimento de futuras classes para a criação dos navegadores de arquivos. Por favor, repare como o navegador de arquivos está organizado no Windows Explorer. Se uma lista hierárquica é exibida no lado esquerdo da janela (ela é chamada de "painel de navegação" no Windows 7), você somente verá as pastas, não arquivos. Todos os arquivos são exibidos na área de conteúdo do Windows Explorer (veja a imagem abaixo).
Fig. 5. Navegador de arquivos no Windows 7. Lista hierárquica à esquerda, a área de conteúdo à direita.
No entanto, geralmente em uma lista hierárquica é necessário exibir os arquivos, além das pastas. Por exemplo, pode-se fazer com que ao selecionar um arquivo em uma lista hierárquica, o seu conteúdo seja exibido na área da direita. Portanto, se o elemento da classe do tipo CTreeView é usado para a criação de um navegador de arquivos, deve ser oferecido ao usuário uma biblioteca com dois modos de seleção: (1) pastas de exibição e arquivos ou (2) apenas as pastas em uma lista hierárquica. Portanto, a enumeração ENUM_FILE_NAVIGATOR_MODE será adicionada à Enums.mqh (veja o código abaixo).
//+------------------------------------------------------------------+ //| Enumeração dos modos de navegação do arquivo | //+------------------------------------------------------------------+ enum ENUM_FILE_NAVIGATOR_MODE { FN_ALL =0, FN_ONLY_FOLDERS =1 };
Nem sempre é necessário que o conteúdo do item seja exibido na área de conteúdo, portanto, nós precisamos ter uma opção para desativá-lo. Por exemplo, isto pode ser útil quando uma lista hierárquica é utilizada como guia na qual os grupos de elementos do controle da biblioteca estão ligados, como foi demonstrado no artigo Interfaces Gráficas VII: O Controle Guias (Capítulo 2).
Além disso, nós também vamos fornecer os modos adicionais para um ajuste mais preciso do controle lista hierárquica. Abaixo está uma lista completa dos modos da versão atual.
- Modo de navegação de arquivos
- Modo de destaque quando o cursor do mouse está em cima
- Modo de exibição do conteúdo do elemento na área de conteúdo
- Modo de alterar a largura da área de conteúdo
- Modo de elementos guia
A fim de definir os modos de elementos, antes dele ser criado nós devemos aplicar os métodos fornecidos no código a seguir:
class CTreeView : public CElement { private: //--- Modo de navegação de arquivos ENUM_FILE_NAVIGATOR_MODE m_file_navigator_mode; //--- Modo de destaque quando o cursor do mouse está em cima bool m_lights_hover; //--- Modo de exibição do conteúdo do elemento na área de conteúdo bool m_show_item_content; //--- Modo de alterar a largura da área de conteúdo bool m_resize_list_area_mode; //--- Modo de elementos guia bool m_tab_items_mode; //--- public: //--- (1) Modo de navegação de arquivos, (2) modo de destaque quando o cursor do mouse estiver em cima // (3) modo de exibir o conteúdo do elemento, (4) o modo de alterar a largura das listas, (5) o modo de elementos guia void NavigatorMode(const ENUM_FILE_NAVIGATOR_MODE mode) { m_file_navigator_mode=mode; } void LightsHover(const bool state) { m_lights_hover=state; } void ShowItemContent(const bool state) { m_show_item_content=state; } void ResizeListAreaMode(const bool state) { m_resize_list_area_mode=state; } void TabItemsMode(const bool state) { m_tab_items_mode=state; } };
A lista abaixo contém as propriedades que estarão disponíveis para definir a aparência externa do elemento.
- Largura da área da lista hierárquica
- Cor de fundo
- Cor da borda do fundo
- Largura da área de conteúdo
- Altura dos elementos nas listas
- Cores do fundo do elemento em diferentes estados
- Cores dos elementos de texto em diferentes estados
- Ícones como indicação de conteúdo no elemento (por padrão, o ícone é definido por uma seta para a direita)
class CTreeView : public CElement { private: //--- Largura de uma área da lista hierárquica int m_treeview_area_width; //--- Cor e a borda do fundo color m_area_color; color m_area_border_color; //--- Largura da área de conteúdo int m_content_area_width; //--- Altura do elemento int m_item_y_size; //--- Cores dos elementos em diferentes estados color m_item_back_color_hover; color m_item_back_color_selected; //--- As cores de texto em diferentes estados color m_item_text_color; color m_item_text_color_hover; color m_item_text_color_selected; //--- Ícones para as setas string m_item_arrow_file_on; string m_item_arrow_file_off; string m_item_arrow_selected_file_on; string m_item_arrow_selected_file_off; //--- public: //--- (1) Altura do elemento, (2) largura de uma lista hierárquica e (3) a lista de conteúdo void ItemYSize(const int y_size) { m_item_y_size=y_size; } void TreeViewAreaWidth(const int x_size) { m_treeview_area_width=x_size; } void ContentAreaWidth(const int x_size) { m_content_area_width=x_size; } //--- Cor de fundo e o quadro do elemento de fundo void AreaBackColor(const color clr) { m_area_color=clr; } void AreaBorderColor(const color clr) { m_area_border_color=clr; } //--- Cores dos elementos em diferentes estados void ItemBackColorHover(const color clr) { m_item_back_color_hover=clr; } void ItemBackColorSelected(const color clr) { m_item_back_color_selected=clr; } //--- As cores de texto em diferentes estados void ItemTextColor(const color clr) { m_item_text_color=clr; } void ItemTextColorHover(const color clr) { m_item_text_color_hover=clr; } void ItemTextColorSelected(const color clr) { m_item_text_color_selected=clr; } //--- Ícones para o elemento seta void ItemArrowFileOn(const string file_path) { m_item_arrow_file_on=file_path; } void ItemArrowFileOff(const string file_path) { m_item_arrow_file_off=file_path; } void ItemArrowSelectedFileOn(const string file_path) { m_item_arrow_selected_file_on=file_path; } void ItemArrowSelectedFileOff(const string file_path) { m_item_arrow_selected_file_off=file_path; } };
Nós vamos exigir os campos para armazenar os índices de elementos destacados em uma lista hierárquica e uma lista de conteúdo. Antes de criar uma lista hierárquica usando o método CTreeView::SelectedItemIndex(), nós podemos selecionar um elemento que deve ser destacado logo após sua criação.
class CTreeView : public CElement { private: //--- Índices dos elementos selecionados nas listas int m_selected_item_index; int m_selected_content_item_index; //--- public: //--- (1) Seleciona o elemento pelo índice e (2) retorna o índice do elemento selecionado void SelectedItemIndex(const int index) { m_selected_item_index=index; } int SelectedItemIndex(void) const { return(m_selected_item_index); } };
Parâmetros para a formação das listas do controle
Antes de criar o elemento do tipo CTreeView, em primeiro lugar, nós precisamos criar uma lista hierárquica. Nós vamos exigir um método para adicionar os elementos com certos parâmetros da lista. Eles serão utilizados para ajudar o programa com a ordem de organizar os elementos no processo de utilização de uma lista hierárquica. Sem alguns parâmetros, será impossível de organizar uma sequência hierárquica. A lista completa desses parâmetros é apresentada abaixo.
- Índice geral de uma lista
Todos os elementos da lista hierárquica são definidas no loop, e o índice de iteração atual está definido para cada item. Enquanto estivermos usando o elemento, independentemente do quão estendido a lista hierárquica está no momento em frente à um usuário, o índice da lista geral para cada elemento permanecerá inicialmente até o fim.
Por exemplo, para os elementos A, B, C, D, E e F os índices gerais da lista serão 0, 1, 2, 3, 4 e 5 respectivamente. Por exemplo, os pontos B e C permanecem no elemento A e o elemento E se encontra no elemento D. Para fins ilustrativos, duas opções de um estado da lista hierárquica são exibidos abaixo. O lado esquerdo da imagem (1) é uma lista totalmente estendida, onde é exibido uma ordem ininterrupta de sua indexação. O lado direito da imagem (2) mostra uma opção quando as listas de elementos A e D que contêm os elementos B, C e E, estão reduzidos, e seus índices anteriores atribuídos durante a formação e inicialização da lista hierárquica de arrays são salvos.
Fig. 6. A Esquerda 1) - lista totalmente estendida. A direita 2) - lista reduzida.
- Índice geral do nó anterior da lista
Por exemplo, os elementos em um catálogo de rota não possuem um nó anterior, portanto, eles terão -1 como parâmetro. Se o elemento tem conteúdo (lista local de elementos), então todos os seus elementos são atribuídos ao índice geral de uma lista de nós onde eles são colocados.
A figura abaixo mostra que os elementos A, D e H em um catálogo raiz possui o valor atribuído -1. Os pontos B e C foram atribuídos à um índice geral [0] do nó anterior A, e por pontos E, F e G - Um índice geral [3] do nó anterior D.
Fig. 7. Todos os elementos filho de um nó tem um índice geral da lista atribuída.
- Descrição do elemento (texto exibido no elemento)
Por exemplo, poderia ser os nomes de pastas e arquivos (se o navegador de arquivo for criado) ou os nomes de categorias e subcategorias, alguns grupos de controles (se o controle de "Guia" for criado).
- Número do nível do nó
A contagem começa do zero. Ou seja, aponta no catálogo raiz, contendo o valor 0. Além disso, enquanto o nível de nidificação é aumentado, o valor para um elemento de nidificação aumenta por uma unidade Para uma melhor compreensão, por favor veja a imagem abaixo. As colunas dos elementos na escala horizontal acima possuem números de seus níveis atribuídos. Nível dos nós A, D e o J mantém o valor 0. Os elementos B, C, E, I e K possuem número 1 e os elementos F, G e H - número 2.
Fig. 8. Um número do nó está associado ao nível de sua nidificação.
- Índice local
Se o elemento tem conteúdo (a sua lista local de elementos), então esta lista terá uma identificação personalizada que começará a partir do zero. Os índices de listas locais são exibidos nas figuras de cor azul no esquema abaixo.
Fig. 9. Indexação das listas locais.
- Índice local de um nó anterior
O mesmo princípio, com um índice geral do nó anterior, é aplicado aqui. Os elementos em um catálogo raiz não possuem um nó anterior, portanto, o valor deste parâmetro é igual a -1. Os elementos que têm um nó anterior possuem seu índice local atribuído a ele. Os índices locais de um nó anterior são marcados em vermelho na imagem abaixo.
Fig. 10. Os índices locais do nó anterior.
- Número de elementos (tamanho do array da lista local do nó).
O tipo (a partir da ENUM_TYPE_TREE_ITEM) atribuído ao elemento no campo da classe CTreeItem depende deste valor. O esquema abaixo mencionado tem uma série de elementos em todas as listas locais fornecidas em uma forma numérica. Ele mostra que os elementos A, D, E e J têm o seguinte conteúdo: (2, 2, 3 e 1 respectivamente), em pontos B, C, F, G, H, I e K estão vazios (0).
Fig. 11. Número de elementos em listas de locais de nós.
- Estado do elemento.
Em caso de uma lista local (conteúdo), ela é recolhida/expandida. Ela permite indicar quais elementos devem ser expandidos logo após a lista hierárquica ser criada.
Por exemplo, todos os parâmetros-chave para definir um elemento em uma lista hierárquica pode ser mostrada agora em uma tabela de resumo simples (veja o exemplo na figura abaixo). Para obter uma interpretação clara, os valores de todos os parâmetros são marcados com cores diferentes.
Fig. 12. Tabela de resumo dos parâmetros-chave (importante) para definir os elementos em uma lista hierárquica.
Ao desenvolver uma classe para a criação de um navegador de arquivos, todos estes parâmetros devem ser calculados automaticamente através da leitura da estrutura hierárquica das pastas de arquivos gerais e locais do terminal. Isso será mostrado em detalhes no próximo capítulo da 8ª série, mas alguns aspectos já serão discutidos, a fim de demonstrar os métodos adaptados da classe lista hierárquica (CTreeView) para a criação dos navegadores de arquivos. Por exemplo, além do número de elementos na lista local do nó, o número de elementos que são pastas também terão que ser indicados quando o navegador de arquivos for criado. Além disso, todo elemento requer um parâmetro que é utilizado para definir ele se trata de uma pasta. No contexto do navegador de arquivos, a ausência da lista local no elemento não significa que ele é um arquivo.
Mas, ao criar lista hierárquica uma simples, a sua estrutura precisa ser criada de forma independente. Em qualquer caso, o mesmo método CTreeView::AddItem() será utilizado para este em ambos os casos. Ele permitirá adicionar um elemento à lista hierárquica com os parâmetros especificados que precisam ser enviados como argumentos. O código a seguir exibe este método e a lista de arrays onde todos os parâmetros dos elementos são armazenados. Por favor note que um ícone exclusivo pode ser definido para cada elemento.
class CTreeView : public CElement { private: //--- Arrays de todos os elementos da lista hierárquica (lista comum) int m_t_list_index[]; int m_t_prev_node_list_index[]; string m_t_item_text[]; string m_t_path_bmp[]; int m_t_item_index[]; int m_t_node_level[]; int m_t_prev_node_item_index[]; int m_t_items_total[]; int m_t_folders_total[]; bool m_t_item_state[]; bool m_t_is_folder[]; //--- public: //--- Adiciona os elementos à lista hierárquica void AddItem(const int list_index,const int list_id,const string item_name,const string path_bmp,const int item_index, const int node_number,const int item_number,const int items_total,const int folders_total,const bool item_state,const bool is_folder=true); }; //+------------------------------------------------------------------+ //| Adiciona o elemento ao array comum de uma lista hierárquica | //+------------------------------------------------------------------+ void CTreeView::AddItem(const int list_index,const int prev_node_list_index,const string item_text,const string path_bmp,const int item_index, const int node_level,const int prev_node_item_index,const int items_total,const int folders_total,const bool item_state,const bool is_folder) { //--- Incrementa o tamanho dos arrays por um elemento int array_size =::ArraySize(m_items); m_items_total =array_size+1; ::ArrayResize(m_items,m_items_total); ::ArrayResize(m_t_list_index,m_items_total); ::ArrayResize(m_t_prev_node_list_index,m_items_total); ::ArrayResize(m_t_item_text,m_items_total); ::ArrayResize(m_t_path_bmp,m_items_total); ::ArrayResize(m_t_item_index,m_items_total); ::ArrayResize(m_t_node_level,m_items_total); ::ArrayResize(m_t_prev_node_item_index,m_items_total); ::ArrayResize(m_t_items_total,m_items_total); ::ArrayResize(m_t_folders_total,m_items_total); ::ArrayResize(m_t_item_state,m_items_total); ::ArrayResize(m_t_is_folder,m_items_total); //--- Armazena os valores dos parâmetros passados m_t_list_index[array_size] =list_index; m_t_prev_node_list_index[array_size] =prev_node_list_index; m_t_item_text[array_size] =item_text; m_t_path_bmp[array_size] =path_bmp; m_t_item_index[array_size] =item_index; m_t_node_level[array_size] =node_level; m_t_prev_node_item_index[array_size] =prev_node_item_index; m_t_items_total[array_size] =items_total; m_t_folders_total[array_size] =folders_total; m_t_item_state[array_size] =item_state; m_t_is_folder[array_size] =is_folder; }
Antes de criar um elemento, é possível definir seus parâmetros e especificar a largura inicial da área da lista hierárquica e da área de conteúdo. Por padrão, o valor no campo da classe onde a largura da área de conteúdo está definida é inicializada por WRONG_VALUE (Veja o código abaixo). Isso significa que se a largura da área de conteúdo não pôde ser definida, então, três componentes do elemento não serão criados: (1) o plano de fundo da área de conteúdo, (2) os arrays de elementos para esta área e (3) as barras de rolagem para a lista nesta área. Isto não irá ocorrer mesmo com o modo "Exibir o conteúdo de um elemento em destaque em uma lista hierárquica" definido nos parâmetros. Portanto, você pode desativar a lista de ser exibida, mantendo apenas o fundo, no entanto, você não pode fazer o contrário.
//+------------------------------------------------------------------+ //| Construtor | //+------------------------------------------------------------------+ CTreeView::CTreeView(void) : m_treeview_area_width(180), m_content_area_width(WRONG_VALUE), m_item_y_size(20), m_visible_items_total(13), m_tab_items_mode(false), m_lights_hover(false), m_show_item_content(true) { //--- Armazena o nome da classe do elemento na classe base CElement::ClassName(CLASS_NAME); //--- Define as prioridades do botão esquerdo do mouse m_zorder=0; }
Uma lista separada arrays dinâmicos serão necessários para a lista de elementos na área do conteúdo. Ele não é tão grande como o da lista hierárquica, já que não é necessário seguir uma sequência hierárquica. Para criar tal lista, apenas três parâmetros serão necessários.
- O índice geral da lista da área de conteúdo
- Índice geral da lista hierárquica
- Descrição do elemento (texto exibido)
class CTreeView : public CElement { private: //--- Arrays para a lista contendo o elementos selecionados em uma lista hierárquica (lista completa) int m_c_list_index[]; int m_c_tree_list_index[]; string m_c_item_text[]; };
A definição dos tamanhos e a inicialização destes arrays serão executados no método de criação da lista de conteúdo - CTreeView::CreateContentItems(). Todos os elementos serão adicionados aos arrays, além daqueles pertencentes ao catálogo raiz, já que não há nenhum nó acima deles que poderia ser selecionado em uma lista hierárquica. Aqui, no início do método, você pode constatar que há uma verificação do modo definido na qual a criação da área de conteúdo depende.
//+------------------------------------------------------------------+ //| Cria uma lista de conteúdo do elemento selecionado | //+------------------------------------------------------------------+ bool CTreeView::CreateContentItems(void) { //--- Sai se o conteúdo do elemento não precisa ser exibido ou // se a área do conteúdo é exibida if(!m_show_item_content || m_content_area_width<0) return(true); //--- Coordenadas e largura int x =m_content_area.X()+1; int y =CElement::Y()+1; int w =m_content_area.X2()-x-1; //--- Contador de elementos int c=0; //--- int items_total=::ArraySize(m_items); for(int i=0; i<items_total; i++) { //--- Elementos do catálogo raiz que não podem estar nessa lista, // portanto, se o nível do nó é inferior a 1, procedemos com o seguinte if(m_t_node_level[i]<1) continue; //--- Incrementa o tamanho dos arrays por um elemento int new_size=c+1; ::ArrayResize(m_content_items,new_size); ::ArrayResize(m_c_item_text,new_size); ::ArrayResize(m_c_tree_list_index,new_size); ::ArrayResize(m_c_list_index,new_size); //--- Cálculo da coordenada Y y=(c>0)? y+m_item_y_size-1 : y; //--- Passa o objeto do painel m_content_items[c].WindowPointer(m_wnd); //--- Define as propriedades antes da criação m_content_items[c].Index(1); m_content_items[c].Id(CElement::Id()); m_content_items[c].XSize(w); m_content_items[c].YSize(m_item_y_size); m_content_items[c].IconFile(m_t_path_bmp[i]); m_content_items[c].ItemBackColor(m_area_color); m_content_items[c].ItemBackColorHover(m_item_back_color_hover); m_content_items[c].ItemBackColorSelected(m_item_back_color_selected); m_content_items[c].ItemTextColor(m_item_text_color); m_content_items[c].ItemTextColorHover(m_item_text_color_hover); m_content_items[c].ItemTextColorSelected(m_item_text_color_selected); //--- Coordenadas m_content_items[c].X(x); m_content_items[c].Y(y); //--- Deslocamentos do elemento extremo do painel m_content_items[c].XGap(x-m_wnd.X()); m_content_items[c].YGap(y-m_wnd.Y()); //--- Cria o objeto if(!m_content_items[c].CreateTreeItem(m_chart_id,m_subwin,x,y,TI_SIMPLE,c,0,m_t_item_text[i],false)) return(false); //--- Oculta o elemento m_content_items[c].Hide(); //--- O elemento será suspenso m_content_items[c].IsDropdown(true); //--- Salva (1) o índice da lista geral de conteúdo, (2) índice da lista hierárquica e (3) o texto do elemento m_c_list_index[c] =c; m_c_tree_list_index[c] =m_t_list_index[i]; m_c_item_text[c] =m_t_item_text[i]; //--- c++; } //--- Salva o tamanho da lista m_content_items_total=::ArraySize(m_content_items); return(true); }
Os arrays dinâmicos da lista hierárquica e a lista na área de conteúdo, que nós discutimos antes, servem para armazenar as listas completas. Mas já que nem todos os elementos dessas listas serão exibidos ao mesmo tempo, será necessário de mais dois grupos de arrays dinâmicos que serão constantemente recriados durante a interação com uma lista hierárquica. Devemos levar em contar que o estado atual dos nós com as listas locais podem ser recolhidas ou expandidas. Vejamos um outro exemplo detalhado.
Por exemplo, nós temos uma lista hierárquica que consiste de 11 elementos:
Fig. 13. Modelo de uma lista hierárquica que consiste de 11 elementos.
Já que os elementos A, D e J estão na lista do catálogo raiz, eles não entrarão na lista completa de elementos na área de conteúdo. A figura abaixo (1) exibe uma lista de todos os elementos da lista hierárquica no lado esquerdo. Os arrays dinâmicos para armazenar os parâmetros desta lista, foram considerados em uma das listas acima, eles possuem em seus nomes o prefixo 't' (abreviação da palavra 'tree' - árvore). No lado direito da imagem é exibido uma (2) lista de elementos se encontram na área de conteúdo. Os arrays dinâmicos para armazenar os parâmetros desta lista contêm em seus nomes o prefixo 'c' (abreviação da palavra 'content' - conteúdo).
Fig. 14. Listas completas para ambos os grupos.
As listas locais nos exemplos observados estão localizados nos elementos A, D, E e J. Na imagem abaixo eles estão marcados em azul.
Fig. 15. Os elementos que contêm as listas locais (marcados em azul).
Toda vez que um elemento é selecionado em uma lista hierárquica que possui conteúdo, a lista da área de conteúdo é recriada. Por exemplo, quando é selecionado o elemento A, é formado a lista de elementos B e C (versão 1 da imagem) na área de conteúdo. Se o elemento D é selecionado, então, a lista de elementos E e I é formado (versão 2 da imagem).
Fig. 16. Exemplo da formação das listas na área de conteúdo.
A recriação da lista de elementos exibidos em uma lista hierárquica é feita no momento da expansão e do recolhimento das listas locais dos nós. É bem simples e óbvio. Os elementos das listas reduzidas não são exibidos e não serão incluídos nos arrays destinados ao armazenamento dos elementos exibidos na lista hierárquica.
Por tudo que foi mencionado acima, será necessário de um array para a lista de elementos exibidos na lista hierárquica que armazenará os índices comuns da lista hierárquica, de três arrays para uma lista de conteúdo que irá armazenar os (1) índices comuns da lista de conteúdo, os (2) índices comuns de uma lista do tipo árvore e (3) a descrição (exibição do texto) dos elementos:
class CTreeView : public CElement { private: //--- Arrays para uma lista de elementos exibidos na lista hierárquica int m_td_list_index[]; //--- Arrays para uma lista de elementos exibidos na lista de conteúdo int m_cd_list_index[]; int m_cd_tree_list_index[]; string m_cd_item_text[]; };
Abaixo, nós vamos discutir os métodos que serão utilizados para formar e gerenciar as listas pelos algoritmos mencionados acima.
Métodos para gerenciar as listas do controle
Após todos os parâmetros serem enviados para a classe CTreeView e o elemento ser criado, nós vamos precisar formar e atualizar as listas de elementos que serão exibidas. Para reduzir o código dos métodos principais, nós criamos os campos privados adicionais e os métodos que serão utilizados para fins relacionados ao trabalho. Nós vamos precisar dos valores dos níveis mínimos e máximos dos nós e do número de elementos no catálogo raiz de uma lista hierárquica.
class CTreeView : public CElement { private: //--- Nível (1) mínimo e (2) máximo do nó int m_min_node_level; int m_max_node_level; //--- Número de elementos no catálogo raiz int m_root_items_total; //--- private: //--- Determina e define (1) os limites dos nós e (2) o tamanho do catálogo raiz void SetNodeLevelBoundaries(void); void SetRootItemsTotal(void); }; //+------------------------------------------------------------------+ //| Determina e define os limites dos nós | //+------------------------------------------------------------------+ void CTreeView::SetNodeLevelBoundaries(void) { //--- Define os níveis mínimos e máximos dos nós m_min_node_level =m_t_node_level[::ArrayMinimum(m_t_node_level)]; m_max_node_level =m_t_node_level[::ArrayMaximum(m_t_node_level)]; } //+------------------------------------------------------------------+ //| Determina e define o tamanho do catálogo raiz | //+------------------------------------------------------------------+ void CTreeView::SetRootItemsTotal(void) { //--- Define o número de elementos no catálogo raiz int items_total=::ArraySize(m_items); for(int i=0; i<items_total; i++) { //--- Se é um nível mínimo, nós podemos aumentar o contador if(m_t_node_level[i]==m_min_node_level) m_root_items_total++; } }
Anteriormente nós estávamos considerando os arrays para a formação das listas de elementos exibidos. Será necessário utilizar o método CTreeView::AddDisplayedTreeItem() para preencher o array de exibição da lista Para adicionar o elemento nessa lista, o índice geral da lista deve ser enviado para este método.
class CTreeView : public CElement { private: //--- Adiciona o elemento à lista na área de conteúdo void AddDisplayedTreeItem(const int list_index); }; //+------------------------------------------------------------------+ //| Adiciona o elemento para o array de elementos exibidos | //| em uma lista hierárquica | //+------------------------------------------------------------------+ void CTreeView::AddDisplayedTreeItem(const int list_index) { //--- Incrementa o tamanho dos arrays por um elemento int array_size=::ArraySize(m_td_list_index); ::ArrayResize(m_td_list_index,array_size+1); //--- Armazena os valores dos parâmetros passados m_td_list_index[array_size]=list_index; }
Após o array ser formado, os elementos devem ser trocados, e o elemento precisa ser redesenhado para exibir as últimas mudanças. Para este efeito, nós vamos criar outro método auxiliar - CTreeView::RedrawTreeList(). O elemento será oculto no início deste método. Então, (1) é calculado a coordenada Y para o primeiro ponto, (2) é corrigido o tamanho da barra de rolagem e (3) calculado a largura dos elementos levando em consideração a presença/ausência da barra de rolagem. Então, a coordenada Y é calculada e atualizado as propriedades para cada elemento no loop de cada iteração. Após o término do loop, o elemento se torna visível novamente.
class CTreeView : public CElement { private: //--- Redesenho da lista hierárquica void RedrawTreeList(void); }; //+------------------------------------------------------------------+ //| Redesenho do controle | //+------------------------------------------------------------------+ void CTreeView::RedrawTreeList(void) { //--- Oculta o elemento Hide(); //--- Coordenada Y do primeiro elemento da lista hierárquica int y=CElement::Y()+1; //--- Obtém o número de elementos m_items_total=::ArraySize(m_td_list_index); //--- Corrige o tamanho da barra de rolagem m_scrollv.ChangeThumbSize(m_items_total,m_visible_items_total); //--- Calcula a largura dos elementos da lista hierárquica int w=(m_items_total>m_visible_items_total) ? CElement::XSize()-m_scrollv.ScrollWidth() : CElement::XSize()-2; //--- Define novos valores for(int i=0; i<m_items_total; i++) { //--- Calcular a coordenada Y para cada elemento y=(i>0)? y+m_item_y_size-1 : y; //--- Obtém o índice comum do elemento na lista int li=m_td_list_index[i]; //--- Atualiza as coordenadas e o tamanho m_items[li].UpdateY(y); m_items[li].UpdateWidth(w); } //--- Exibe o elemento Show(); }
Todos os campos métodos listados acima serão chamados no método CTreeView::UpdateTreeViewList() onde a lista hierárquica será formada e atualizada (veja o código abaixo). Vamos ver esse método com mais detalhes
Quatro arrays dinâmicos locais serão necessários e eles serão utilizados para controlar a ordem dos elementos no processo de formação da lista. Entre eles estão os seguintes arrays:
- índices comuns da lista de nós anteriores;
- índices locais de elementos;
- número de elementos em um nó;
- número de pastas em um nó.
O tamanho inicial dos arrays serão de dois elementos a mais que o número máximo de nós de uma lista hierárquica. Isso é necessário para que os valores do elemento atual (no loop) sejam memorizados no próximo elemento dos arrays, e os valores do elemento anterior são verificados na próxima iteração pelo índice atual do loop. Inicialmente os arrays são inicializados com -1. Por favor, note que todas as vezes que o programa entrar neste método, o buffer do array m_td_list_index[], necessário para criar a lista hierárquica, é liberado, e seu tamanho é definido para zero. Nós também precisamos de um contador adicional de elementos (ii) e uma variável (end_list) para definir um sinalização de último elemento no catálogo raiz.
Depois de declarar todas os arrays e variáveis locais, o loop principal do método CTreeView::UpdateTreeViewList() começa sua operação. Ele vai funcionar até que:
- o contador do nó (nl) exceda a máxima definida;
- nós não atingimos o último elemento no catálogo raiz (após verificar todos os elementos inseridos lá);
- o usuário não removeu o programa.
Este é um loop duplo. Os nós da lista hierárquica são calculados no primeiro nível. Todos os elementos da lista local do nó atual são verificados no segundo nível. No início do corpo do segundo loop, o modo de navegação do arquivo é verificado, no caso da lista hierárquica ser utilizada para este propósito. Se o modo "exibir apenas as pastas na lista hierárquica" for ativado, então deve-se verificar se o elemento atual é uma pasta, e avançar para o elemento seguinte, caso não seja.
Se estas condições forem satisfeitas, serão realizadas mais três verificações. O programa irá avançar para o elemento seguinte nos três casos mencionados abaixo.
- Se os nós não coincidirem.
- A sequência dos índices locais de elementos não está sendo seguida.
- Se não estamos no loop do catalogo, e o índice geral da lista do nó anterior não for igual àquela semelhante, que está armazenada na memória.
Após passar em todas as verificações listadas acima, nós memorizamos o índice local do elemento, se o elemento seguinte exceder o tamanho da lista local.
Além disso, se acontecer do elemento conter uma lista local e ela estar expandida no momento, nós vamos adicionar o elemento ao array de elementos exibidos na lista hierárquica. Aqui, você deve memorizar os valores dos elementos atuais para os arrays locais do método. A única exceção aqui é para o índice local de um elemento. Ele deve ser salvo no índice atual do nó (nl), e os valores de todos os parâmetros restantes em — no índice do próximo nó (n). Além disso, o contador de elementos do índice local é zerado aqui, e o loop atual (segundo) para prosseguir para o próximo nó é finalizado aqui.
Se o movimento para o próximo nó falhar, significa que ele era um elemento sem uma lista ou a lista está atualmente reduzida. Nesse caso, o elemento é adicionado primeiramente ao array de elementos exibidos em uma lista hierárquica. Em seguida, é incrementado o contador de índices locais elementos.
Se agora nós estamos no catálogo raiz, e atingimos o último elemento da lista, isso significa que a lista foi formada com sucesso. A sinalização está definida e o loop é interrompido. Se nós ainda não atingimos o último elemento no catálogo raiz, nós obteremos um número de elementos no nó atual ou o número de pastas, caso o respectivo modo está definido. Se este não é o último elemento da lista, nós vamos passar para o próximo. Se chegamos até o último elemento, então nós temos de avançar para o nó anterior e continuar com o loop do elemento que foi verificado pela última vez em sua lista. Nós procedemos assim até o último elemento do catálogo raiz ser alcançado.
Após a lista hierárquica ser criada, o elemento é redesenhado no final do método.
class CTreeView : public CElement { private: //--- Atualiza a lista hierárquica void UpdateTreeViewList(void); }; //+------------------------------------------------------------------+ //| Atualiza a lista hierárquica | //+------------------------------------------------------------------+ void CTreeView::UpdateTreeViewList(void) { //--- Arrasy para controlar a ordem dos elementos: int l_prev_node_list_index[]; // índice comum da lista do nó anterior int l_item_index[]; // índice local do elemento int l_items_total[]; // número de elementos no nó int l_folders_total[]; // número de pastas no nó //--- Define o tamanho inicial dos arrays int begin_size=m_max_node_level+2; ::ArrayResize(l_prev_node_list_index,begin_size); ::ArrayResize(l_item_index,begin_size); ::ArrayResize(l_items_total,begin_size); ::ArrayResize(l_folders_total,begin_size); //--- Inicialização dos arrays ::ArrayInitialize(l_prev_node_list_index,-1); ::ArrayInitialize(l_item_index,-1); ::ArrayInitialize(l_items_total,-1); ::ArrayInitialize(l_folders_total,-1); //--- Esvazia o array de elementos exibidos na lista hierárquica ::ArrayFree(m_td_list_index); //--- Contador dos índices locais de elementos int ii=0; //--- Para definir um sinalizador do último elemento no catálogo raiz bool end_list=false; //--- Coleta os elementos exibidos dentro do array O loop funcionará até que: // 1: o contador nó não exceder o valor máximo; // 2: atingir o último elemento (após a verificação de todos os elementos da entrada); // 3: o usuário remove o programa. int items_total=::ArraySize(m_items); for(int nl=m_min_node_level; nl<=m_max_node_level && !end_list; nl++) { for(int i=0; i<items_total && !::IsStopped(); i++) { //--- Se o modo "exibir apenas as pastas" estiver ativado if(m_file_navigator_mode==FN_ONLY_FOLDERS) { //--- Se for um arquivo, então, avança para o próximo elemento if(!m_t_is_folder[i]) continue; } //--- Se (1) ele não é nosso nó ou (2) a ordem dos índices locais não estiverem na sequência // nós procedemos para o próximo elemento if(nl!=m_t_node_level[i] || m_t_item_index[i]<=l_item_index[nl]) continue; //--- Passe para o próximo elemento, se ele não estiver no catálogo raiz e // o índice geral da lista do nó anterior não for igual àquele semelhante armazenado na memória if(nl>m_min_node_level && m_t_prev_node_list_index[i]!=l_prev_node_list_index[nl]) continue; //--- Guarda o índice local do elemento, se o próximo elemento exceder o tamanho da lista local if(m_t_item_index[i]+1>=l_items_total[nl]) ii=m_t_item_index[i]; //--- Se a lista do elemento atual está maximizada if(m_t_item_state[i]) { //--- Adiciona o elemento ao array de elementos na lista hierárquica AddDisplayedTreeItem(i); //--- Guarda os valores atuais e avança para o próximo nó int n=nl+1; l_prev_node_list_index[n] =m_t_list_index[i]; l_item_index[nl] =m_t_item_index[i]; l_items_total[n] =m_t_items_total[i]; l_folders_total[n] =m_t_folders_total[i]; //--- Zera o contador de índices locais de elementos ii=0; //--- Avança para o próximo nó break; } //--- Adiciona o elemento ao array de elementos na lista hierárquica AddDisplayedTreeItem(i); //--- Aumenta o contador de índices locais de elementos ii++; //--- Se atingiu o último elemento no catálogo raiz if(nl==m_min_node_level && ii>=m_root_items_total) { //--- Define o sinalizador e encerra o loop atual end_list=true; break; } //--- Se não tivermos alcançado o último elemento no catálogo raiz else if(nl>m_min_node_level) { //--- Obtém o número de elementos no nó atual int total=(m_file_navigator_mode==FN_ONLY_FOLDERS)? l_folders_total[nl]: l_items_total[nl]; //--- Se não for o último índice local do elemento, nós passamos para o próximo if(ii<total) continue; //--- Se atingimos o último índice local, então, // nós precisamos voltar para o nó anterior e proceder a partir do elemento de onde paramos while(true) { //--- Reseta os valores do nó atual nos arrays listados abaixo l_prev_node_list_index[nl] =-1; l_item_index[nl] =-1; l_items_total[nl] =-1; //--- Diminui o número de nós, enquanto a igualdade no número de elementos na lista local for mantida // ou que não tenha atingido o catálogo raiz if(l_item_index[nl-1]+1>=l_items_total[nl-1]) { if(nl-1==m_min_node_level) break; //--- nl--; continue; } //--- break; } //--- Procede para o nó anterior nl=nl-2; //--- Zera o contador de índices locais de elementos e avança para o próximo nó ii=0; break; } } } //--- Redesenha o elemento RedrawTreeList(); }
O deslocamento das listas a respeito das barras de rolagem serão realizadas através de métodos CTreeView::ShiftTreeList() e CTreeView::ShiftContentList(). A lógica principal destes métodos é praticamente idêntica, por conseguinte, nós vamos fornecer apenas um dos códigos como exemplo (para a lista hierárquica).
No início do método todos os elementos da lista hierárquica são ocultados. Em seguida, é calculado a largura dos elementos levando em consideração a barra de rolagem na lista atual. Se há uma barra de rolagem, então, é determinada a posição do cursor. Então, as coordenadas e a largura são calculadas e atualizadas no loop para cada elemento, e o elemento se torna visível. No final do processo, no caso da barra de rolagem precisar ser exibida, ela deve ser redesenhada para ser colocada por cima da lista.
class CTreeView : public CElement { private: //--- Deslocamento das listas void ShiftTreeList(void); void ShiftContentList(void); }; //+------------------------------------------------------------------+ //| Desloca a lista hierárquica contra a barra de rolagem | //+------------------------------------------------------------------+ void CTreeView::ShiftTreeList(void) { //--- Oculta todos os elementos da lista hierárquica int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) m_items[i].Hide(); //--- Se a barra de rolagem é necessária bool is_scroll=m_items_total>m_visible_items_total; //--- Calcula a largura dos elementos da lista hierárquica int w=(is_scroll)? m_area.XSize()-m_scrollv.ScrollWidth()-1 : m_area.XSize()-2; //--- Definindo a posição da barra de rolagem int v=(is_scroll)? m_scrollv.CurrentPos() : 0; m_scrollv.CurrentPos(v); //--- Coordenada Y do primeiro elemento da lista hierárquica int y=CElement::Y()+1; //--- for(int r=0; r<m_visible_items_total; r++) { //--- Verifica para evitar o array de exceder o tamanho permitido if(v>=0 && v<m_items_total) { //--- Calcula a coordenada Y y=(r>0)? y+m_item_y_size-1 : y; //--- Obtém o índice comum do elemento da lista hierárquica int li=m_td_list_index[v]; //--- Define as coordenadas e a largura m_items[li].UpdateX(m_area.X()+1); m_items[li].UpdateY(y); m_items[li].UpdateWidth(w); //--- Mostra o elemento m_items[li].Show(); v++; } } //--- Redesenha a barra de rolagem if(is_scroll) m_scrollv.Reset(); }
O método para a formação e atualização da lista de conteúdo é consideravelmente mais fácil, já que uma lista regular é formada aqui e não requer que siga uma ordem hierárquica. Aqui, no início do método CTreeView::UpdateContentList(), os arrays que ajudam a formar a lista de conteúdo são liberados. Então, nós iteramos sobre todos os elementos da lista hierárquica no primeiro loop e guardamos apenas os elementos que têm os seguintes parâmetros correspondentes com o elemento selecionado na lista hierárquica:
- Níveis dos nós
- Índices locais de elementos
- Índices comuns de elementos.
Este loop apenas armazena a descrição do elemento (texto exibido) e o índice geral da lista hierárquica.
No segundo loop, nós devemos iterar sobre a lista de conteúdo e preencher o array para os índices gerais desta lista. A fim de determinar os elementos necessários, serão usados os parâmetros que foram obtidos no primeiro loop. No final do método, o tamanho da barra de rolagem é corrigida, e é atualizada a lista para exibir as últimas alterações.
class CTreeView : public CElement { private: //--- Atualiza a lista de conteúdo void UpdateContentList(void); }; //+------------------------------------------------------------------+ //| Atualiza a lista de conteúdo | //+------------------------------------------------------------------+ void CTreeView::UpdateContentList(void) { //--- Índice do elemento selecionado int li=m_selected_item_index; //--- Libera o array da lista de conteúdo ::ArrayFree(m_cd_item_text); ::ArrayFree(m_cd_list_index); ::ArrayFree(m_cd_tree_list_index); //--- Forma a lista de conteúdo int items_total=::ArraySize(m_items); for(int i=0; i<items_total; i++) { //--- Se (1) os níveis de nós e (2) os índices locais de elementos corresponderem, bem como // (3) o índice de um nó anterior coincidir com o índice de um elemento selecionado if(m_t_node_level[i]==m_t_node_level[li]+1 && m_t_prev_node_item_index[i]==m_t_item_index[li] && m_t_prev_node_list_index[i]==li) { //--- Aumenta o array de elementos exibidos na lista de conteúdo int size =::ArraySize(m_cd_list_index); int new_size =size+1; ::ArrayResize(m_cd_item_text,new_size); ::ArrayResize(m_cd_list_index,new_size); ::ArrayResize(m_cd_tree_list_index,new_size); //--- Armazena o texto do elemento e o índice geral da lista hierárquica nos arrays m_cd_item_text[size] =m_t_item_text[i]; m_cd_tree_list_index[size] =m_t_list_index[i]; } } //--- Se, eventualmente, a lista não está vazia, nós preenchemos o array de índices gerais da lista de conteúdo int cd_items_total=::ArraySize(m_cd_list_index); if(cd_items_total>0) { //--- Contador de elementos int c=0; //--- Itera sobre a lista int c_items_total=::ArraySize(m_c_list_index); for(int i=0; i<c_items_total; i++) { //--- Se a descrição e os índices comuns de elementos da lista hierárquica corresponderem if(m_c_item_text[i]==m_cd_item_text[c] && m_c_tree_list_index[i]==m_cd_tree_list_index[c]) { //--- Armazena o índice geral da lista de conteúdo e procede pra a próxima m_cd_list_index[c]=m_c_list_index[i]; c++; //--- Sai do loop, se chegamos ao fim da lista exibida if(c>=cd_items_total) break; } } } //--- Corrige o tamanho da barra de rolagem m_content_scrollv.ChangeThumbSize(cd_items_total,m_visible_items_total); //--- Corrije a lista de conteúdo do elemento ShiftContentList(); }
Gerenciando a largura das áreas da lista
Agora, nós vamos olhar com mais detalhes, como o modo de alteração da largura das listas operam. O que é exigido: (1) um método privado principal CTreeView::ResizeListArea(), onde serão realizadas todas as principais verificações e com base nos resultados dessas verificações — altera a largura das listas e também (2) de quatro métodos privados auxiliares para resolver as tarefas mencionadas abaixo.
- O método CTreeView::CheckXResizePointer() — verifica a prontidão para alterar a largura das listas. Aqui, o código do método consiste de dois blocos ligados a um termo. Se o ponteiro do cursor não está ativado, mas o cursor do mouse está em sua área, as coordenadas do ponteiro são atualizadas, tornando-se visível. Se o botão esquerdo do mouse é pressionado, então o ponteiro é ativado. Se não for realizado a primeira condição do método, então, o ponteiro do cursor é desativado e ocultado.
class CTreeView : public CElement { private: //--- Verifica a prontidão para alterar a largura das listas void CheckXResizePointer(const int x,const int y); }; //+------------------------------------------------------------------+ //| Verifica a prontidão para alterar a largura das listas | //+------------------------------------------------------------------+ void CTreeView::CheckXResizePointer(const int x,const int y) { //--- Se o ponteiro não está ativado, mas o cursor do mouse está em sua área if(!m_x_resize.State() && y>m_area.Y() && y<m_area.Y2() && x>m_area.X2()-2 && x<m_area.X2()+3) { //--- Atualiza as coordenadas do ponteiro e torna-o visível int l_x=x-m_x_resize.XGap(); int l_y=y-m_x_resize.YGap(); m_x_resize.Moving(l_x,l_y); m_x_resize.Show(); //--- Define a visibilidade do sinalizador m_x_resize.IsVisible(true); //--- Se o botão esquerdo do mouse é pressionado if(m_mouse_state) //--- Ativa o ponteiro m_x_resize.State(true); } else { //--- Se o botão esquerdo do mouse é pressionado if(!m_mouse_state) { //--- Desativa e oculta o ponteiro m_x_resize.State(false); m_x_resize.Hide(); //--- Remove a visibilidade do sinalizador m_x_resize.IsVisible(false); } } }
- O método CTreeView::CheckOutOfArea() é um teste para ir além das restrições. Não há razão em alterar a largura das listas para excluir completamente um deles da visibilidade. Portanto, nós vamos definir a restrição para 80 pixels. Vamos organizá-lo de forma que, se o cursor vai além da restrição estabelecida na horizontal, então, o deslocando do ponteiro será somente possível na vertical, mas dentro da área de conexão das áreas das listas.
class CTreeView : public CElement { private: //--- Verificação para ir além das restrições bool CheckOutOfArea(const int x,const int y); }; //+------------------------------------------------------------------+ //| Verificação para ir além das restrições | //+------------------------------------------------------------------+ bool CTreeView::CheckOutOfArea(const int x,const int y) { //--- Restrição int area_limit=80; //--- Se formos além do limite horizontal do elemento... if(x<m_area.X()+area_limit || x>m_content_area.X2()-area_limit) { // ... desloca o ponteiro apenas na vertical, sem sair dos limites if(y>m_area.Y() && y<m_area.Y2()) m_x_resize.UpdateY(y-m_x_resize.YGap()); //--- Não muda a largura das listas return(false); } //--- Altera a largura das listas return(true); }
- Os métodos CTreeView::UpdateTreeListWidth() e CTreeView::UpdateContentListWidth() são para atualizar (1) a largura da lista hierárquica e (2) a largura da lista na área de conteúdo. Apenas a borda da direita será deslocada na lista hierárquica. Para este efeito, apenas a sua largura necessita ser alterada. Além disso, é necessário atualizar as coordenadas da barra de rolagem. Para obter uma lista na área de conteúdo, você precisa atualizar sua largura e a coordenada X simultaneamente para conseguir somente o deslocamento da borda esquerda.
class CTreeView : public CElement { private: //--- Atualiza a largura da lista hierárquica void UpdateTreeListWidth(const int x); //--- Atualiza a largura da lista na área de conteúdo void UpdateContentListWidth(const int x); }; //+------------------------------------------------------------------+ //| Atualiza a largura da lista hierárquica | //+------------------------------------------------------------------+ void CTreeView::UpdateTreeListWidth(const int x) { //--- Calcula e define a largura da lista hierárquica m_area.X_Size(x-m_area.X()); m_area.XSize(m_area.X_Size()); //--- Calcula e define uma largura para os elementos na exibição da árvore levando em consideração a barra de rolagem int l_w=(m_items_total>m_visible_items_total) ? m_area.XSize()-m_scrollv.ScrollWidth()-4 : m_area.XSize()-1; int items_total=::ArraySize(m_items); for(int i=0; i<items_total; i++) m_items[i].UpdateWidth(l_w); //--- Calcula e define as coordenadas para a barra de rolagem da lista hierárquica m_scrollv.X(m_area.X2()-m_scrollv.ScrollWidth()); m_scrollv.XDistance(m_scrollv.X()); } //+------------------------------------------------------------------+ // Atualiza a largura da lista na área de conteúdo | //+------------------------------------------------------------------+ void CTreeView::UpdateContentListWidth(const int x) { //--- Calcula e define a coordenada X, deslocamento e largura da área de conteúdo int l_x=m_area.X2()-1; m_content_area.X(l_x); m_content_area.X_Distance(l_x); m_content_area.XGap(l_x-m_wnd.X()); m_content_area.XSize(CElement::X2()-m_content_area.X()); m_content_area.X_Size(m_content_area.XSize()); //--- Calcula e define a coordenada X e a largura para os elementos na lista de conteúdo l_x=m_content_area.X()+1; int l_w=(m_content_items_total>m_visible_items_total) ? m_content_area.XSize()-m_content_scrollv.ScrollWidth()-4 : m_content_area.XSize()-2; int total=::ArraySize(m_content_items); for(int i=0; i<total; i++) { m_content_items[i].UpdateX(l_x); m_content_items[i].UpdateWidth(l_w); } }
Todos estes métodos auxiliares serão chamados no método principal CTreeView::ResizeListArea() que será usado eventualmente no tratamento dos eventos da aplicação. Várias verificações precisam ser feitas no início do método. O programa irá sair daqui nos casos mencionados abaixo.
- Se o modo para alterar a largura das listas está desativado
- Se a área de conteúdo está desativada
- Se o modo elementos guia está habilitado
- Se a barra de rolagem está ativa (o controle deslizante está no modo deslocamento)
Se todas estas verificações forem concluídas, então, o método CTreeView::CheckXResizePointer() é chamado para determinar a prontidão do estado para alterar a largura das listas. Se ele eventualmente o ponteiro ficar desativado, então nós devemos desbloquear a formulário na qual ele foi bloqueado anteriormente.
Se o ponteiro estiver ativado, nós precisamos ver primeiro se nós vamos para além das restrições especificadas. Se sim, o programa terminará neste método. Se estamos na área de trabalho, então, o formulário é bloqueado. O identificador do elemento deve ser salvo, porque somente o elemento que o bloqueou possui a opção para desbloquear o formulário. As coordenadas do ponteiro do cursor são então atualizadas, juntamente com as coordenadas das listas e sua largura.
//+------------------------------------------------------------------+ //| Gerencia a largura das listas | //+------------------------------------------------------------------+ void CTreeView::ResizeListArea(const int x,const int y) { //--- Sai, (1) se a largura da área de conteúdo não deve ser alterada ou // (2) se a área de conteúdo está desativado ou (3) o modo elementos guia está habilitado if(!m_resize_list_area_mode || m_content_area_width<0 || m_tab_items_mode) return; //--- Sai, se a barra de rolagem está ativa if(m_scrollv.ScrollState()) return; //--- Verifica a prontidão para alterar a largura das listas CheckXResizePointer(x,y); //--- Desbloqueia o formulário se o ponteiro está desativado if(!m_x_resize.State()) { //--- Somente quem bloqueou o formulário é que pode desbloqueá-lo if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()==CElement::Id()) { m_wnd.IsLocked(false); m_wnd.IdActivatedElement(WRONG_VALUE); return; } } else { //--- Verificação da saída para além das restrições especificadas if(!CheckOutOfArea(x,y)) return; //--- Bloqueia o formulário e guarda o identificador do elemento ativo m_wnd.IsLocked(true); m_wnd.IdActivatedElement(CElement::Id()); //--- Define a coordenada X para o objeto no meio do cursor do mouse m_x_resize.UpdateX(x-m_x_resize.XGap()); //--- A coordenada Y é definida apenas se não formos para além da área do elemento if(y>m_area.Y() && y<m_area.Y2()) m_x_resize.UpdateY(y-m_x_resize.YGap()); //--- Atualiza a largura da lista hierárquica UpdateTreeListWidth(x); //--- Atualiza a largura da lista de conteúdo UpdateContentListWidth(x); //--- Atualzia as coordenadas e os tamanhos das listas ShiftTreeList(); ShiftContentList(); //--- Redesenha o ponteiro m_x_resize.Reset(); } }
Para o resultado intercalar, nós podemos fornecer o bloco de código do manipulador do elemento CTreeView::OnEvent() onde o evento de mover o mouse CHARTEVENT_MOUSE_MOVE é tratado. Muitos métodos que têm sido considerados acima, são usadas especificamente aqui.
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CTreeView::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Manipulação do evento do movimento do cursor if(id==CHARTEVENT_MOUSE_MOVE) { //--- Sai se o elemento está oculto if(!CElement::IsVisible()) return; //--- Coordenadas e o estado do botão esquerdo do mouse int x=(int)lparam; int y=(int)dparam; m_mouse_state=(bool)int(sparam); //--- Verificação do foco sobre a lista CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); //--- Desloca lista hierárquica se o gerenciamento do controle deslizante da barra de rolagem está em ação if(m_scrollv.ScrollBarControl(x,y,m_mouse_state)) { ShiftTreeList(); return; } //--- Desloca a lista de conteúdo se o gerenciamento do controle deslizante da barra de rolagem está em ação if(m_content_scrollv.ScrollBarControl(x,y,m_mouse_state)) { ShiftContentList(); return; } //--- Gestão da largura da área de conteúdo ResizeListArea(x,y); //--- Sai se o formulário está bloqueado if(m_wnd.IsLocked()) return; //--- Altera a cor quando o mouse estiver sobre ele ChangeObjectsColor(); return; } }
Modo de elementos guia
Nós vamos tentar descobrir como o modo da guia irá operar em uma lista hierárquica. No momento atual, a biblioteca já possui duas classes para a criação das guias: CTabs e CIconTabs. Informações mais detalhadas sobre como eles serão projetados está disponível no artigo Interfaces Gráficas VII: O Controle Guias (Capítulo 2). Nestas classes são criadas as guias em uma linha horizontal e vertical. Em uma lista hierárquica, os elementos podem ser organizadas por categorias, sendo mais conveniente quando há uma grande quantidade de elementos em uma interface gráfica da aplicação e eles precisam ser agrupados de alguma forma.
Assim, se na criação da lista hierárquica o modo elementos guias foi definido, então, após todos os objetos do elemento serem criados, deve-se determinar quais elementos da lista hierárquica serão guias. As guias podem ser apenas os elementos que não possuem listas locais. Deve-se considerar que as guias de elementos terão a sua ordem de indexação (veja a figura abaixo). Esta ordem específica dos índices deve ser focada ao adicionar os controles para os elementos guia.
A figura abaixo mostra um exemplo da ordem de indexação dos elementos guia. Os pontos A, D, G e H não são guias, pois eles possuem listas.
Fig. 17. Índices dos elementos guia
A fim de resolver esta questão, nós vamos precisar de uma estrutura (com a declaração do array de suas instâncias), que terá um array dinâmico para armazenar os ponteiros aos elementos e um campo onde o índice da guia será armazenado:
class CTreeView : public CElement { private: //--- Estrutura de elementos atribuídos a cada elemento guia struct TVElements { CElement *elements[]; int list_index; }; TVElements m_tab_items[]; };
A fim de determinar quais os pontos que serão guias e também para formar o seu array, o método CTreeView::GenerateTabItemsArray() será utilizado. Se o modo de elementos guia está desativado, então o programa irá sair imediatamente deste método. Então, nós vamos interagir sobre toda a lista hierárquica e um elemento simples é encontrado a cada vez, nós vamos diminuir o array da estrutura TVElements por um elemento e manter o índice geral do elemento.
Então, se o conteúdo do elemento é exibido, então, o primeiro elemento da lista será selecionado por padrão. Se a exibição do conteúdo do elemento está desativado, então, no caso de sair do intervalo, o índice é corrigido. O elemento guia indicado nas propriedades é então selecionado.
class CTreeView : public CElement { private: //--- Forma o array de elementos guia void GenerateTabItemsArray(void); }; //+------------------------------------------------------------------+ //| Forma o array dos elementos guia | //+------------------------------------------------------------------+ void CTreeView::GenerateTabItemsArray(void) { //--- Sair se o modo elemento guia está desativado if(!m_tab_items_mode) return; //--- Adiciona apenas os elementos vazios ao array de elementos guias int items_total=::ArraySize(m_items); for(int i=0; i<items_total; i++) { //--- Se este elemento tem outros elementos, nós procedemos para o próximo if(m_t_items_total[i]>0) continue; //--- Aumenta o tamanho do array de elementos guia por um elemento int array_size=::ArraySize(m_tab_items); ::ArrayResize(m_tab_items,array_size+1); //--- Armazena o índice geral de elementos m_tab_items[array_size].list_index=i; } //--- Se a exibição do conteúdo do elemento está desativado if(!m_show_item_content) { //--- Recebe o tamanho do array do elemento guia int tab_items_total=::ArraySize(m_tab_items); //--- Corrige o índice ao sair do intervalo if(m_selected_item_index>=tab_items_total) m_selected_item_index=tab_items_total-1; //--- Desativa a seleção do elemento atual na lista m_items[m_selected_item_index].HighlightItemState(false); //--- Índice da guia selecionada int tab_index=m_tab_items[m_selected_item_index].list_index; m_selected_item_index=tab_index; //--- Seleciona este elemento m_items[tab_index].HighlightItemState(true); } }
Para conectar qualquer controle à guia da lista hierárquica, o método CTreeView::AddToElementsArray() deve ser utilizado. Este método tem dois argumentos: (1) o índice do elemento guia e (2) o objeto do tipo CElement cujo ponteiro deverá ser armazenado no array do elemento guia especificado.
class CTreeView : public CElement { public: //--- Adiciona o elemento ao array do elemento guia void AddToElementsArray(const int item_index,CElement &object); }; //+------------------------------------------------------------------+ //| Adiciona o elemento para o array da guia especificada | //+------------------------------------------------------------------+ void CTreeView::AddToElementsArray(const int tab_index,CElement &object) { //--- Verifica se o tamanho não foi excedido int array_size=::ArraySize(m_tab_items); if(array_size<1 || tab_index<0 || tab_index>=array_size) return; //--- Adiciona o ponteiro do elemento enviado ao array da guia especificada int size=::ArraySize(m_tab_items[tab_index].elements); ::ArrayResize(m_tab_items[tab_index].elements,size+1); m_tab_items[tab_index].elements[size]=::GetPointer(object); }
O método CTreeView::ShowTabElements() é usado para exibir os elementos de apenas os elementos guia selecionados. O programa sairá do método, se for verificado que o elemento está oculto ou o modo está desativado. Então, no primeiro loop do método, é determinado o índice do elemento guia selecionado. Então, no segundo loop, são exibidos apenas os elementos que foram atribuídos a guia selecionada, e o restante é ocultado.
class CTreeView : public CElement { public: //--- Exibe somente os elementos selecionados do elemento guia void ShowTabElements(void); }; //+------------------------------------------------------------------+ //| Exibe somente os elementos selecionados do elemento guia | //+------------------------------------------------------------------+ void CTreeView::ShowTabElements(void) { //--- Sai se o elemento está oculto ou o modo de elementos guia está desativado if(!CElement::IsVisible() || !m_tab_items_mode) return; //--- Índice da guia selecionada int tab_index=WRONG_VALUE; //--- Define o índice da guia selecionada int tab_items_total=::ArraySize(m_tab_items); for(int i=0; i<tab_items_total; i++) { if(m_tab_items[i].list_index==m_selected_item_index) { tab_index=i; break; } } //--- Exibe os elementos de apenas uma guia selecionada for(int i=0; i<tab_items_total; i++) { //--- Obtém o número de elementos ligados à guia int tab_elements_total=::ArraySize(m_tab_items[i].elements); //--- Se este ponto da guia é selecionado if(i==tab_index) { //--- Mostrar os elementos for(int j=0; j<tab_elements_total; j++) m_tab_items[i].elements[j].Reset(); } else { //--- Oculta os elementos for(int j=0; j<tab_elements_total; j++) m_tab_items[i].elements[j].Hide(); } } }
Métodos para o tratamento de eventos
Ao selecionar um elemento da lista, uma mensagem será gerada informando que o caminho para o elemento da lista hierárquica foi alterado. Posteriormente, este evento poderia ser aceito nos navegadores de arquivos para determinar o caminho para o arquivo. A fim de implementar o que nós temos planejado, nós vamos exigir que o identificador ON_CHANGE_TREE_PATH esteja no arquivo Defines.mqh(veja o código abaixo):
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #define ON_CHANGE_TREE_PATH (23) // Se o caminho para a lista hierárquica foi alterado
A fim de determinar um caminho para o elemento selecionado, nós vamos escrever um método público CTreeView::CurrentFullPath() que será chamado em um navegador de arquivos para obter o caminho do arquivo. Será necessário os campos e um método para para receber o arquivo selecionado nas listas do elemento. Eles serão relevantes somente quando a lista hierárquica é utilizada como um componente do navegador de arquivos. Vamos considerar os detalhes do método CTreeView::CurrentFullPath().
O caminho é formado dentro do array path_parts[], através da adição consecutiva dos elementos nele, desde o elemento selecionado no momento atual, até a hierarquia da lista. No início, verifique se o elemento selecionado é um arquivo. Se for uma pasta, então, nós adicionamos ao array (os nomes dos arquivo não serão adicionados ao caminho).
Então, nós iteramos sobre toda a lista hierárquica do elemento selecionado para cima. Isto é implementado em conformidade. Nós ignoramos todos os elementos que são arquivos. A fim de adicionar o elemento ao array, todas as três condições precisam ser verdadeiras.
- O índice de uma lista geral precisa combinar com o índice da lista geral do nó anterior.
- O índice do elemento da lista local precisa coincidir com o índice do elemento do nó anterior.
- A ordem decrescente dos nós deve ser seguida.
Se todas as todas as três condições satisfazerem: (1) o nome de um elemento é adicionado ao array, (2) o índice atual do loop é salvo par uma verificação posterior e (3) o contador do loop é resetado. Mas no caso de nós atingirmos o nível zero de nós, o loop é interrompido.
Em seguida, no loop separado a string é formada (o caminho completo para um elemento selecionado) com a adição do separador "\\". Além disso, se o elemento selecionado em uma lista hierárquica é uma pasta, nós vamos verificar se o elemento selecionado está presente na área da lista de conteúdo e se ele é um arquivo. Se o modo "exibir arquivos na lista hierárquica" está ativado, então, quando o arquivo for selecionado, seu nome é imediatamente salvo após a formação da string.
No final do método a string que contém um caminho para elemento selecionado é retornada. Se o arquivo também está selecionado na categoria atual, então ele pode ser obtido usando o método público CTreeView::SelectedItemFileName().
class CTreeView : public CElement { private: //--- Texto do elemento selecionado na lista. // Apenas para arquivos em caso da utilização de uma classe para a criação de um navegador de arquivos. // Se não for um arquivo selecionado na lista, então este campo deve ter uma string vazia"". string m_selected_item_file_name; //--- public: //--- Return the file name string SelectedItemFileName(void) const { return(m_selected_item_file_name); } //--- Retornar o caminho completo do elemento selecionado string CurrentFullPath(void); }; //+------------------------------------------------------------------+ //| Retorna o caminho atual completo | //+------------------------------------------------------------------+ string CTreeView::CurrentFullPath(void) { //--- Para a formação de uma pasta para o elemento selecionado string path=""; //--- Índice do elemento selecionado int li=m_selected_item_index; //--- Array para a formação de uma pasta string path_parts[]; //--- Recebe a descrição (texto) do elemento selecionado na lista hierárquica, // mas apenas se ele for uma pasta if(m_t_is_folder[li]) { ::ArrayResize(path_parts,1); path_parts[0]=m_t_item_text[li]; } //--- Itera sobre a lista inteira int total=::ArraySize(m_t_list_index); for(int i=0; i<total; i++) { //--- Somente as pastas são considerados. // Se não houver um arquivo, nós passamos para o próximo elemento. if(!m_t_is_folder[i]) continue; //--- Se o índice de uma lista geral corresponde ao índice de uma lista geral do nó anterior e // o índice do elemento de uma lista local corresponde ao índice de um elemento do nó anterior, // então a ordem dos níveis do nó é mantida if(m_t_list_index[i]==m_t_prev_node_list_index[li] && m_t_item_index[i]==m_t_prev_node_item_index[li] && m_t_node_level[i]==m_t_node_level[li]-1) { //--- Aumenta o array por um elemento e salva a descrição do elemento int sz=::ArraySize(path_parts); ::ArrayResize(path_parts,sz+1); path_parts[sz]=m_t_item_text[i]; //--- Memoriza o índice para a próxima verificação li=i; //--- Se nós tivermos atingido o nível zero do nó, encerra o loop if(m_t_node_level[i]==0 || i<=0) break; //--- Reseta o contador do loop i=-1; } } //--- Forma uma string - caminho completo para o elemento selecionado em uma lista hierárquica total=::ArraySize(path_parts); for(int i=total-1; i>=0; i--) ::StringAdd(path,path_parts[i]+"\\"); //--- Se uma pasta é um elemento selecionado em uma lista hierárquica if(m_t_is_folder[m_selected_item_index]) { m_selected_item_file_name=""; //--- Se um elemento foi selecionado na área de conteúdo if(m_selected_content_item_index>0) { //--- Se o elemento selecionado é um arquivo, armazenamos o seu nome if(!m_t_is_folder[m_c_tree_list_index[m_selected_content_item_index]]) m_selected_item_file_name=m_c_item_text[m_selected_content_item_index]; } } //--- Se o elemento selecionado na lista hierárquica é um arquivo else //--- Armazena o seu nome m_selected_item_file_name=m_t_item_text[m_selected_item_index]; //--- Retorna a pasta return(path); }
Três ações do usuário serão tratadas na versão atual do controle lista hierárquica.
- Pressionando o botão para reduzir/expandir a lista local do elemento - o método CTreeView::OnClickItemArrow(). Se acontecer do nome do objeto gráfico não ser da lista hierárquica ou se o identificador do elemento não coincidir com o identificador da lista hierárquica no nome do objeto, o programa sairá do método. Então, (1) nós vamos precisamos obter o estado do botão do elemento e alterá-lo para o estado oposto, (2) atualizar a lista hierárquica, exibindo as últimas alterações, (3) calcular a posição do controle deslizante da barra de rolagem e (4) no caso do modo relevante estar habilitado, exibir somente os elementos guia selecionados no momento atual.
class CTreeView : public CElement { private: //--- Trata o pressionamento do botão de reduzir/expandir a lista de elementos bool OnClickItemArrow(const string clicked_object); }; //+------------------------------------------------------------------+ //| Pressionamento do botão de reduzir/expandir a lista de elementos | //+------------------------------------------------------------------+ bool CTreeView::OnClickItemArrow(const string clicked_object) { //--- Sai, se ele tem um nome de objeto diferente if(::StringFind(clicked_object,CElement::ProgramName()+"_0_treeitem_arrow_",0)<0) return(false); //--- Obtém o identificador do nome do objeto int id=IdFromObjectName(clicked_object); //--- Sai, se os identificadores não corresponderem if(id!=CElement::Id()) return(false); //--- Obtém o índice do elemento na lista geral int list_index=IndexFromObjectName(clicked_object); //--- Obtém o estado do elemento seta e define uma oposta m_t_item_state[list_index]=!m_t_item_state[list_index]; ((CChartObjectBmpLabel*)m_items[list_index].Object(1)).State(m_t_item_state[list_index]); //--- Atualiza a lista hierárquica UpdateTreeViewList(); //--- Calcula a posição do controle deslizante da barra de rolagem m_scrollv.CalculateThumbY(); //--- Mostra os elementos da guia selecionada ShowTabElements(); return(true); }
- Clicando no elemento da lista hierárquica - o método CTreeView::OnClickItem(). Aqui, no início houve vários controles que faziam a verificação, o mesmo que no elemento anterior da descrição. Entre outros, a saída do método é executada quando uma das barras de rolagem do elemento é ativa no momento atual.
Em seguida, no loop nós iteramos sobre a parte visível da lista hierárquica. Nós encontramos o elemento que foi pressionado. Se acontecer do elemento já estar selecionado, então, o programa sairá do método. Se ele não for selecionado, no caso de quando o modo de elementos guia estar ativado e a exibição do conteúdo do elemento selecionado estar desativado, mas o elemento não contém uma lista, o loop é imediatamente interrompido e nenhuma alteração é feita. Caso contrário, o elemento que foi pressionado é selecionado.
Após o loop, se atingirmos este momento, um novo elemento é selecionado. Isso significa que deve ser resetado o índice do elemento selecionado na lista da área de conteúdo e sua cor. Por exemplo, esta é a forma como ele é implementado no Windows Explorer. Em seguida, a lista na área de conteúdo é atualizada para exibir as últimas alterações. No final, a mensagem com o identificador do evento ON_CHANGE_TREE_PATH é gerada, podendo ser tratada no manipulador de eventos de uma aplicação personalizada ou algum outro elemento.
class CTreeView : public CElement { private: //--- Lida com o clique sobre o elemento da lista hierárquica bool OnClickItem(const string clicked_object); }; //+------------------------------------------------------------------+ //| Clicando no elemento da lista hierárquica | //+------------------------------------------------------------------+ bool CTreeView::OnClickItem(const string clicked_object) { //--- Sai se a barra de rolagem está ativa if(m_scrollv.ScrollState() || m_content_scrollv.ScrollState()) return(false); //--- Sai, se ele tem um nome de objeto diferente if(::StringFind(clicked_object,CElement::ProgramName()+"_0_treeitem_area_",0)<0) return(false); //--- Obtém o identificador do nome do objeto int id=IdFromObjectName(clicked_object); //--- Sai, se os identificadores não corresponderem if(id!=CElement::Id()) return(false); //--- Obtém a posição atual do deslizador da barra de rolagem int v=m_scrollv.CurrentPos(); //--- Itera sobre a lista for(int r=0; r<m_visible_items_total; r++) { //--- Verifica para evitar o array de exceder o tamanho permitido if(v>=0 && v<m_items_total) { //--- Obtém o índice geral do elemento int li=m_td_list_index[v]; //--- Se este elemento for selecionado na lista if(m_items[li].Object(0).Name()==clicked_object) { //--- Sai se este elemento já foi selecionado if(li==m_selected_item_index) return(false); //--- Se o modo elemento guia está habilitado, e o modo de exibição de conteúdo estiver desativado, // Nós não vamos selecionar os elementos sem uma lista if(m_tab_items_mode && !m_show_item_content) { //--- Se o elemento atual não contém uma lista, nós vamos parar o loop if(m_t_items_total[li]>0) break; } //--- Define a cor ao elemento selecionado anteriormente m_items[m_selected_item_index].HighlightItemState(false); //--- Lembra do índice para o atual e altera a sua cor m_selected_item_index=li; m_items[li].HighlightItemState(true); break; } v++; } } //--- Reseta as cores na área de conteúdo if(m_selected_content_item_index>=0) m_content_items[m_selected_content_item_index].HighlightItemState(false); //--- Reseta o elemento selecionado m_selected_content_item_index=WRONG_VALUE; //--- Renova a lista de conteúdo UpdateContentList(); //--- Calcula a posição do controle deslizante da barra de rolagem m_content_scrollv.CalculateThumbY(); //--- Corrige a lista de conteúdo ShiftContentList(); //--- Mostra os elementos da guia selecionada ShowTabElements(); //--- Enviar mensagens sobre a seleção de um novo diretório na lista hierárquica ::EventChartCustom(m_chart_id,ON_CHANGE_TREE_PATH,0,0,""); return(true); }
- Pressione o elemento na área da lista de conteúdo - o método CTreeView::OnClickContentListItem(). O código desse método é semelhante ao descrito no elemento anterior da descrição e até mesmo significativamente mais simples, portanto, nós não vamos mencioná-lo aqui. Deve-se notar apenas que ao clicar no elemento na área de conteúdo, o evento com o identificador ON_CHANGE_TREE_PATH também é gerado, que, por exemplo, em um navegador de arquivos isso poderia implicar a seleção de arquivos.
Eventualmente, o processo de interação com o elemento através dos métodos considerados anteriormente neste artigo, ambas as listas (se a área de conteúdo está habilitada) serão recriadas "no caminho". O bloco de código no manipulador de eventos do elemento com os métodos considerados acima podem ser cuidadosamente assimilados na listagem a seguir:
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CTreeView::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Manipulação do evento do movimento do cursor //... //--- Manipulação de eventos do clique esquerdo do mouse em um objeto if(id==CHARTEVENT_OBJECT_CLICK) { //--- Sai se no modo de alterar o tamanho da área da lista de conteúdo if(m_x_resize.IsVisible() || m_x_resize.State()) return; //--- Manipula o clique do elemento seta if(OnClickItemArrow(sparam)) return; //--- Lida com o clique sobre o elemento da lista hierárquica if(OnClickItem(sparam)) return; //--- Lida com o clique sobre o elemento área de conteúdo if(OnClickContentListItem(sparam)) return; //--- Desloca a lista contra a barra de rolagem if(m_scrollv.OnClickScrollInc(sparam) || m_scrollv.OnClickScrollDec(sparam)) ShiftTreeList(); if(m_content_scrollv.OnClickScrollInc(sparam) || m_content_scrollv.OnClickScrollDec(sparam)) ShiftContentList(); //--- return; } }
A primeira versão da classe para criar o controle lista hierárquica está completamente pronto. Mas para conseguirmos sua correta operação em todos os seus modos, nós devemos integrar na biblioteca do cursor.
Integração do controle ao motor da biblioteca
Os arquivos com as classes lista hierárquica devem ser conectados ao arquivo WndContainer.mqh (veja o código abaixo). Nós vamos adicionar um array personalizado da lista hierárquica para a estrutura do array de elemento. Além disso, são necessários os métodos para o recebimento do número de três listas hierárquicas e para salvar os ponteiros desses elementos que fazem parte da lista de exibição de árvore. O código destes métodos podem ser mais estudados nos arquivos anexados no final deste artigo.
#include "TreeItem.mqh" #include "TreeView.mqh" //+------------------------------------------------------------------+ //| Classe para armazenar todos os objetos da interface | //+------------------------------------------------------------------+ class CWndContainer { protected: //--- Array da janela CWindow *m_windows[]; //--- Estrutura dos arrays de elementos struct WindowElements { //--- Listas hierárquicas CTreeView *m_treeview_lists[]; }; //--- Array dentado de elementos para cada janela WindowElements m_wnd[]; //--- public: //--- Número da lista de vistas int TreeViewListsTotal(const int window_index); //--- private: //--- Armazena os ponteiros aos elementos da lista hierárquica bool AddTreeViewListsElements(const int window_index,CElement &object); };
Algumas adições devem ser inseridos na classe derivada CWndEvents da classe CWndContainer, onde os eventos de biblioteca principal são manipulados. Primeiro de tudo, ao expandir o formulário no caso quando uma árvore é usada como guia para agrupamento de elementos em uma interface gráfica, é necessário exibir os elementos somente para o selecionado no momento atual. Portanto, o código deve ser adicionado ao método CWndEvents::OnWindowUnroll(), como é mostrado na lista abaixo. Para esta casos particulares os elementos são distribuídos entre os seus arrays privados. Em vez da iteração sobre todos os arrays de todos os elementos, é suficiente iterar sobre os arrays privados daqueles que são necessários em uma situação específica.
//+------------------------------------------------------------------+ //| Evento ON_WINDOW_UNROLL | //+------------------------------------------------------------------+ bool CWndEvents::OnWindowUnroll(void) { //--- Se há o sinal de "Maximizar a janela" if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_UNROLL) return(false); //--- Índice da janela ativa int awi=m_active_window_index; //--- Se o identificador da janela e o número da sub-janela coincidirem if(m_lparam==m_windows[awi].Id() && (int)m_dparam==m_subwin) { int elements_total=CWndContainer::ElementsTotal(awi); for(int e=0; e<elements_total; e++) { //--- Faça todos os elementos visíveis para além do formulário e ... if(m_wnd[awi].m_elements[e].ClassName()!="CWindow") { //--- ... esses elementos em uma lista suspensa if(!m_wnd[awi].m_elements[e].IsDropdown()) m_wnd[awi].m_elements[e].Show(); } } //--- Se há somente guias ,então , mostra apenas os elementos selecionados int tabs_total=CWndContainer::TabsTotal(awi); for(int t=0; t<tabs_total; t++) m_wnd[awi].m_tabs[t].ShowTabElements(); //--- Se houver três listas hierárquicas, então, mostra os elementos de apenas uma guia selecionada int treeview_total=CWndContainer::TreeViewListsTotal(awi); for(int tv=0; tv<treeview_total; tv++) m_wnd[awi].m_treeview_lists[tv].ShowTabElements(); } //--- Atualiza a localização de todos os elementos MovingWindow(); m_chart.Redraw(); return(true); }
O mesmo loop deve ser adicionado ao método CWndEvents::OnOpenDialogBox() logo após o loop semelhante para os elementos do tipo CTabs Tipo (guia). Nós não vamos fornecer este código aqui para poupar algum espaço no artigo.
Ele também é importante para esvaziar o array privado para as listas hierárquicas. A seguinte sequência de caracteres devem ser adicionadas ao método CWndEvents::Destroy(), uma vez que ele também é realizado por outras arrays privados de controles:
::ArrayFree(m_wnd[w].m_treeview_lists);
Teste do controle lista hierárquica
Tudo parece estar pronto para testar a lista hierárquica. Nós vamos usar o EA do artigo anterior e limpá-lo a partir dele todos os elementos para além do menu principal e o estado da string. Além disso, nós declaramos a instância da classe CTreeView, um método para criar a lista hierárquica e os deslocamentos do ponto extremo que o elemento será ligado:
class CProgram : public CWndEvents { private: //--- Lista hierárquica CTreeView m_treeview; //--- private: //--- Lista hierárquica #define TREEVIEW1_GAP_X (2) #define TREEVIEW1_GAP_Y (43) bool CreateTreeView(void); };
Como exemplo, nós criamos uma lista hierárquica que foi apresentada no início da figura 12 neste artigo (veja o código abaixo). Há 25 elementos na lista no total. Um número de elementos visíveis não excede 10. Na mesma seção do artigo, houve uma clarificação de que ao criar tal lista, seus parâmetros devem ser especificados de forma independente. Antes de formar os arrays com os parâmetros para cada elemento, é melhor exibi-los todos em um editor de tabela. Tal visualização simples irá simplificar a tarefa e reduzir os riscos de cometer um erro.
Nós vamos definir as imagens para cada grupo de elementos. O primeiro elemento conterá a lista. Por exemplo, vamos desenrolá-lo após criarmos o elemento (estado true), e as listas de outros elementos que eles possuem, será mantido de forma reduzida (estado false). Nós vamos ativar os modos para (1) destacar o elemento quando o mouse estiver sobre ele, (2) exibir o conteúdo do elemento na área adjacente ou (3) mudar a largura das áreas da lista.
Após todas as propriedades do elemento estarem definidas, nós vamos adicionar os elementos com os parâmetros indicados nos arrays para a lista usando o método CTreeView::AddItem(). A lista hierárquica é criada depois, e o ponteiro para ela é salvo na base dos controles.
//+------------------------------------------------------------------+ //| Cria uma lista hierárquica | //+------------------------------------------------------------------+ bool CProgram::CreateTreeView(void) { //--- Número de elementos na lista hierárquica #define TREEVIEW_ITEMS 25 //--- Armazena o ponteiro para a janela m_treeview.WindowPointer(m_window1); //--- Coordenadas int x=m_window1.X()+TREEVIEW1_GAP_X; int y=m_window1.Y()+TREEVIEW1_GAP_Y; //--- Forma os arrays para a lista hierárquica: // Imagens para os elementos #define A "Images\\EasyAndFastGUI\\Icons\\bmp16\\advisor.bmp" // Expert Advisor #define I "Images\\EasyAndFastGUI\\Icons\\bmp16\\indicator.bmp" // Indicator #define S "Images\\EasyAndFastGUI\\Icons\\bmp16\\script.bmp" // Script string path_bmp[TREEVIEW_ITEMS]= {A,I,I,I,I,I,I,I,I,S,S,S,S,S,S,S,S,S,S,S,S,S,S,A,A}; //--- Descrição dos elementos (texto exibido) string item_text[TREEVIEW_ITEMS]= {"Advisor01","Indicators","01","02","03","04","05","06","07", "Scripts","01","02","03","04","05","06","07","08","09","10","11","12","13", "Advisor02","Advisor03"}; //--- Índice da lista geral de nós anteriores int prev_node_list_index[TREEVIEW_ITEMS]= {-1,0,1,1,1,1,1,1,1,0,9,9,9,9,9,9,9,9,9,9,9,9,9,-1,-1}; //--- Índices de elementos das listas locais int item_index[TREEVIEW_ITEMS]= {0,0,0,1,2,3,4,5,6,1,0,1,2,3,4,5,6,7,8,9,10,11,12,1,2}; //--- Número de níveis do nó int node_level[TREEVIEW_ITEMS]= {0,1,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0}; //--- Índices locais de elementos de nós anteriores int prev_node_item_index[TREEVIEW_ITEMS]= {-1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,-1,-1}; //--- Número de elementos das listas locais int items_total[TREEVIEW_ITEMS]= {2,7,0,0,0,0,0,0,0,13,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; //--- Estado da lista de elementos bool item_state[TREEVIEW_ITEMS]= {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; //--- Define as propriedades antes da criação m_treeview.TreeViewAreaWidth(180); m_treeview.ContentAreaWidth(0); m_treeview.VisibleItemsTotal(10); m_treeview.LightsHover(true); m_treeview.ShowItemContent(true); m_treeview.ResizeListAreaMode(true); //--- Propriedades da barra de rolagem m_treeview.GetScrollVPointer().AreaBorderColor(clrLightGray); m_treeview.GetContentScrollVPointer().AreaBorderColor(clrLightGray); //--- Adiciona os pontos for(int i=0; i<TREEVIEW_ITEMS; i++) m_treeview.AddItem(i,prev_node_list_index[i],item_text[i],path_bmp[i], item_index[i],node_level[i],prev_node_item_index[i],items_total[i],0,item_state[i],true); //--- Criar uma lista hierárquica if(!m_treeview.CreateTreeView(m_chart_id,m_subwin,x,y)) return(false); //--- Adiciona o ponteiro ao elemento da base CWndContainer::AddToElementsArray(0,m_treeview); return(true); }
Chamar esse método deve ser realizada no método principal de criar uma interface gráfica da aplicação MQL. Neste caso, este método é chamado de CProgram::CreateExpertPanel().
Vamos compilar o programa e executar o EA no gráfico. O resultado que, eventualmente, devemos obter é apresentado na figura. Parece que o primeiro elemento é expandido e selecionado. E seus conteúdos aparecem na área à direita.
Fig. 18. Teste do controle lista hierárquica. Apenas o primeiro elemento da é expandido.
A fim de exibir a você, nós vamos expandir as listas de todos os elementos. Portanto, o botão da seta do elemento deve ser pressionado. Nós fazemos isso e selecionamos o elemento com a descrição "Scripts", a fim de exibir a lista na área de conteúdo. O resultado é mostrado na figura abaixo. Parece que quando um número de elementos não se encaixa na faixa de 10 elementos visíveis, aparecem as barras de rolagem. O terceiro elemento é selecionado na área de conteúdo. Nós também podemos ver que, quando o cursor do mouse passa sobre a borda que liga as duas áreas das listas de elementos, um ponteiro do usuário (erro de duplo lado) do cursor do mouse aparece.
Fig. 19. Listas de todos os elementos são expandidos.
Vamos criar outro EA para testes, onde o modo elementos guia é mostrado. Nós fazemos três listas expandidas pelo esquema descrito na figura abaixo:
Fig. 20. Esquema lista hierárquica
Nós garantimos os elementos como o CCheckBox e a CTable em favor da lista de elementos "Advisors" e "Indicators. Os elementos guia da lista "Scripts" permanecem vazio, assim você tem a oportunidade de praticar. Nós não vamos usar o código inteiro. Nós só devemos notar que os modos de chave e as propriedades serão usadas para esta opção: (1) o modo de elementos guia está ativado, (2) a exibição do conteúdo dos elementos está desativada e (3) um terceiro elemento guia é selecionado.
//... m_treeview.TabItemsMode(true); m_treeview.LightsHover(true); m_treeview.ShowItemContent(false); m_treeview.SelectedItemIndex((m_treeview.SelectedItemIndex()==WRONG_VALUE) ? 3 : m_treeview.SelectedItemIndex()); //--- Propriedades da barra de rolagem //...
Nós apenas precisamos compilar e carregar o programa no gráfico. A tela abaixo mostra o resultado:
Fig. 21. Teste do modo de elementos guia.
Conclusão
Neste artigo (segundo capítulo da oitava parte da série de artigos), nós consideramos um dos elementos mais complicadas da biblioteca da interface gráfica - o controle lista hierárquica. O artigo fornece três classes de elementos:
- A classe CPointer para a criação de um ponteiro do usuário para o cursor do mouse.
- A classe CTreeItem para a criação de um elemento da lista hierárquica.
- A classe CTreeView para a criação da lista hierárquica.
No próximo artigo, nós vamos desenvolver este assunto mais a fundo e vamos criar uma classe de código muito benéfica, que irá facilitar o processo de criação do navegador de arquivo em sua aplicação MQL.
Mas abaixo estão todos os materiais da parte 7 da série de artigos onde você pode baixar e realizar os seus testes. 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.
Lista de artigos (capítulos) da parte 8:
- Interfaces Gráficas VIII: O Controle Calendário (Capítulo 1)
- Interfaces Gráficas VIII: O Controle Lista Hierárquica (Capítulo 2)
- Interfaces Gráficas VIII: O Controle Navegador de Arquivos (Capítulo 3)
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2539
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso