English Русский 中文 Deutsch 日本語 Português
Interfaces gráficas VIII: Control "Lista jerárquica" (Capítulo 2)

Interfaces gráficas VIII: Control "Lista jerárquica" (Capítulo 2)

MetaTrader 5Ejemplos | 28 julio 2016, 16:13
980 0
Anatoli Kazharski
Anatoli Kazharski

Índice

 

 

Introducción

El primer artículo de la serie nos cuenta con más detalles para qué sirve esta librería: Interfaces gráficas I: Preparación de la estructura de la librería (Capítulo 1). Al final de cada artículo de la serie se muestra la lista completa de los capítulos con los enlaces. Además, se puede descargar la versión completa de la librería en la fase actual del desarrollo del proyecto. Es necesario colocar los ficheros en los mismos directorios, tal como están ubicados en el archivo.

En el capítulo anterior de la octava parte de la serie sobre las interfaces gráficas hemos analizado los controles “Calendario estático” y “Calendario desplegable”. El segundo capítulo va a dedicarse a un control compuesto no menos complejo, “Lista jerárquica”, sin la que no se arregla ninguna librería multifuncional para la creación de interfaces gráficas. La implementación de la lista jerárquica presentada en este artículo contiene múltiples ajustes y modos flexibles, lo que permitirá configurar este control a sus necesidades con la máxima precisión. 

Aparte de eso, en este artículo describiremos la implementación de otra clase útil cuya tarea será la creación de los punteros para el cursor del ratón. Además, demostraremos el ejemplo del uso de este tipo del control en la lista jerárquica.

 

 

Control “Lista jerárquica (lista en forma de árbol)”

A diferencia de las listas habituales, la lista jerárquica permite distribuir sus elementos por las categorías a los niveles de anidación ilimitados. Cada elemento (punto o nodo) que incluye la lista de otros elementos (uno o más) está previsto de un control mediante el cual se puede ocultar o mostrar la lista local. Aparte de la descripción de texto, cada elemento puede disponer de un icono (un pictograma), lo que hace que la lista de este tipo sea intuitiva al máximo para el usuario. Los puntos finales (es decir, los que no tienen listas locales) pueden incluir grupos de controles para la visualización en la interfaz gráfica de la aplicación, o realizar alguna función.

El control “lista jerárquica” puede utilizarse para la creación de los catálogos cuyos elementos están vinculados con relaciones jerárquicas. Por ejemplo, a través de esta lista se puede crear el explorador de archivos, lo mismo que está hecho en los exploradores de los sistemas operativos. En los terminales de trading MetaTrader, así como en el editor del código MetaEditor, también hay ventanas-navegadores que utilizan listas jerárquicas. 

En el terminal de trading MetaTrader, se puede mostrar/ocultar la ventana “Navegador” usando la combinación de teclas ‘Ctrl+N’: 

 

Fig. 1. Ventana “Navegador” en el terminal de trading MetaTrader.

En el editor del código MetaEditor, se utiliza la combinación de teclas ‘Ctrl+D’ para mostrar/ocultar la ventana “Navegador”: 

 

Fig. 2. Ventana “Navegador” en el editor del código MetaEditor.

 

 

Desarrollo de la clase CTreeItem para la creación del elemento de la lista jerárquica

Antes de empezar el desarrollo de la clase de la lista jerárquica, hay que crear dos controles más. Uno de ellos es el más importante: el elemento de la lista jerárquica. Según su organización, parece en algo en el elemento del menú contextual (CMenuItem) que hemos analizado en el artículo Interfaces gráficas II: Control “Elemento del menú” (Capítulo 1), pero posee particularidades únicas por eso requiere la creación de una clase separada. 

El segundo control es el puntero para el cursor del ratón. Va a utilizarse como indicador del estado de disponibilidad del cambio del ancho de las áreas de las listas. Para crear este tipo del control, escribiremos la clase separada CPointer que luego puede utilizarse en otros controles. Hablaremos de la clase CPointer en la siguiente sección de este artículo.

Primero, veremos qué objetos van a formar un elemento de la lista jerárquica.

Los componentes del elemento de la lista jerárquica son los siguientes:

  1. Fondo
  2. Indicio de la presencia de la lista local de elementos. Se utilizan las imágenes en forma de las flechas o pictogramas más/menos que reflejan el estatus de la lista (expandida/reducida).
  3. Icono del elemento. Por ejemplo, puede ser útil para asignarlo visualmente a alguna categoría. 
  4. Descripción textual del elemento.



 

Fig. 3. Componentes del elemento de la lista jerárquica.

Creamos el archivo TreeItem.mqh y lo incluimos en la librería (archivo WndContainer.mqh):

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "TreeItem.mqh"

Ahora, en el archivo TreeItem.mqh, hay que crear la clase CTreeItem con los métodos estándar para todos los controles de la librería (véase el código de abajo):

//+------------------------------------------------------------------+
//|                                                     TreeItem.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
//+------------------------------------------------------------------+
//| Clase para crear el elemento de la lista jerárquica                    |
//+------------------------------------------------------------------+
class CTreeItem : public CElement
  {
private:
   //--- Puntero al formulario al que está adjuntado el control
   CWindow          *m_wnd;
   //---
public:
                     CTreeItem (void);
                    ~CTreeItem (void);
   //--- Guarda el puntero del formulario
   void              WindowPointer(CWindow &object)             { m_wnd=::GetPointer(object);            }
   //---
public:
   //--- Manejador de eventos del gráfico
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Temporizador
   virtual void      OnEventTimer(void) {}
   //--- Desplazamiento del control
   virtual void      Moving(const int x,const int y);
   //--- (1) Mostrar, (2) ocultar, (3) resetear, (4) eliminar
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) Establecer, (2) resetear las prioridades para el clic izquierdo del ratón
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
   //--- Resetear el color
   virtual void      ResetColors(void);
  };

Antes de la creación del control, estarán disponibles las siguientes propiedades para ajustar su apariencia:

  • Color del fondo del elemento en diferentes estados
  • Icono del elemento
  • Imágenes para la flecha que muestra el estatua actual (expandida/reducida) de la lista local del elemento
  • Márgenes de la etiqueta de texto
  • Colores del texto en estados diferentes
class CTreeItem : public CElement
  {
private:
   //--- Color del fondo en diferentes estados
   color             m_item_back_color;
   color             m_item_back_color_hover;
   color             m_item_back_color_selected;
   //--- Imágenes para la flecha del elemento
   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;
   //--- Icono del elemento
   string            m_icon_file;
   //--- Márgenes de la etiqueta de texto
   int               m_label_x_gap;
   int               m_label_y_gap;
   //--- Colores del texto en estados diferentes del elemento
   color             m_item_text_color;
   color             m_item_text_color_hover;
   color             m_item_text_color_selected;
   //---
public:
   //--- Colores del fondo del elemento
   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) establecer el icono para el elemento, (2) establecer imágenes para la flecha del elemento
   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) Devuelve el texto del elemento, (2) establecer los márgenes para la etiqueta 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;                      }
   //--- Colores del texto en estados diferentes
   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;           }
  };

Para crear el elemento de la lista jerárquica vamos a necesitar cuatro métodos privados (private) y uno público (public): Preste atención que aquí vamos a utilizar el objeto gráfico tipo CEdit como la etiqueta de texto. Más abajo hablaremos con más detalles para qué es necesario.

class CTreeItem : public CElement
  {
private:
   //--- Objetos para crear el elemento de la lista jerárquica
   CRectLabel        m_area;
   CBmpLabel         m_arrow;
   CBmpLabel         m_icon;
   CEdit             m_label;
   //---
public:
   //--- Métodos para crear el elemento de la lista jerárquica
   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);
  };

Los valores para los parámetros a base de los cuales el elemento se posiciona en la lista respecto a otros elementos deben pasarse al método público CTreeItem::CreateTreeItem() Algunos de ellos van a participar en la formación del nombre de objetos gráficos que formarán parte del elemento. Con los valores de los parámetros pasados se inicializan los campos correspondientes en la clase que luego se utilizan en los métodos privados para la creación de los objetos del elemento. 

Las propiedades principales son las siguientes.

  • El tipo del elemento puede seleccionarse de dos opciones: (1) elemento simple (TI_SIMPLE), que no contiene otros elementos siendo un nodo final, y (2) elemento que incluye otros elementos (TI_HAS_ITEMS). Para eso insertamos la enumeración ENUM_TYPE_TREE_ITEM en el archivo Enums.mqh:  
//+------------------------------------------------------------------+
//| Enumeración de los tipos del elemento de la lista jerárquica                     |
//+------------------------------------------------------------------+
enum ENUM_TYPE_TREE_ITEM
  {
   TI_SIMPLE    =0,
   TI_HAS_ITEMS =1
  };
  • Índice general en la lista.
  • Nivel (número) del nodo.
  • Texto a mostrar (descripción).
  • Estado del elemento.

Aquí hay que prestar atención en cómo se calcula el margen para la flecha (indicio de la presencia de la lista local en el elemento). Posteriormente, cada nodo en la lista jerárquica se desplazará del nodo anterior a la distancia calculada en esta línea. 

class CTreeItem : public CElement
  {
private:
   //--- Margen para la flecha (indicio de la presencia de la lista)
   int               m_arrow_x_offset;
  //--- Tipo del elemento
   ENUM_TYPE_TREE_ITEM m_item_type;
   //--- Índice del elemento en la lista general
   int               m_list_index;
   //--- Nivel del nodo
   int               m_node_level;
   //--- Texto a mostrar en el elemento
   string            m_item_text;
   //--- Estado de la lista del elemento (expandida/reducida)
   bool              m_item_state;
  };
//+------------------------------------------------------------------+
//| Crea el elemento de la lista jerárquica                                |
//+------------------------------------------------------------------+
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)
  {
//--- Salir si no hay puntero al formulario
   if(::CheckPointer(m_wnd)==POINTER_INVALID)
     {
      ::Print(__FUNCTION__," > Antes de crear el elemento de la lista, a la clase hay que pasarle "
              "el puntero al formulario: CTreeItem::WindowPointer(CWindow &object)");
      return(false);
     }
//--- Inicialización de variables
   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;
//--- Márgenes desde el punto extremo
   CElement::XGap(CElement::X()-m_wnd.X());
   CElement::YGap(CElement::Y()-m_wnd.Y());
//--- Creación del elemento del menú
   if(!CreateArea())
      return(false);
   if(!CreateArrow())
      return(false);
   if(!CreateIcon())
      return(false);
   if(!CreateLabel())
      return(false);
//--- Ocultar el elemento si es la ventana de diálogo o está minimizada
   if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized())
      Hide();
//---
   return(true);
  }

Los nombres de los objetos del elemento van a formarse de los siguientes componentes (véase el código de abajo):

  • Nombre del programa
  • Índice del elemento
  • Indicio de la pertenencia al elemento («treeitem»)
  • Indicio de la pertenencia a una parte del elemento
  • Índice general del elemento de la lista jerárquica
  • Identificador del elemento

Como ejemplo, mostraremos uno de los métodos privados para la creación de los objetos integrantes del elemento: CTreeItem::CreateArrow(). Por defecto, la librería ya contiene las imágenes para el indicio de la lista desplegable. Puede descargarlas desde el archivo adjunto al final del artículo. Si hace falta, se puede cambiarlos antes de la creación del elemento. 

El margen desde el lado izquierdo del elemento para este objeto se calcula usando el valor del parámetro m_node_level (nivel del nodo) en el método principal para la creación del elemento antes de la creación de los objetos del elemento. Si resulta que es el tipo simple del elemento (TI_SIMPLE), el progrrama sale del método. Fíjese que es obligatorio guardar las coordenadas para este objeto antes de comprobar el tipo del elemento (antes de la posible salida del método) porque ellas van a usarse para el cálculo de las coordenadas de los siguientes objetos del elemento en la cola. El mismo principio se utiliza en el método CTreeItem::CreateIcon() para la creación del icono del elemento. 

Aquí mismo, después de la creación del objeto, se le asigna el estado de acuerdo con el valor item_state que ha sido pasado al método principal. Este valor permite controlar qué puntos deben estar abiertas después de la creación de la lista jerárquica. 

//+------------------------------------------------------------------+
//| Crea la flecha (indicio de lista desplegable)                     |
//+------------------------------------------------------------------+
#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)
  {
//--- Calculamos las coordenadas
   int x =CElement::X()+m_arrow_x_offset;
   int y =CElement::Y()+2;
//--- Guardamos las coordenadas para el cálculo de las coordenadas de los siguientes objetos del elemento en la cola
   m_arrow.X(x);
   m_arrow.Y(y);
//--- Salimos si el elemento no tiene lista desplegable
   if(m_item_type!=TI_HAS_ITEMS)
      return(true);
//--- Formación del nombre del objeto
   string name=CElement::ProgramName()+"_"+(string)CElement::Index()+"_treeitem_arrow_"+(string)m_list_index+"__"+(string)CElement::Id();
//--- Actualizamos las imágenes predefinidas
   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";
//--- Establecemos el objeto
   if(!m_arrow.Create(m_chart_id,name,m_subwin,x,y))
      return(false);
//--- Establecemos las propiedades
   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");
//--- Márgenes desde el punto extremo
   m_arrow.XGap(x-m_wnd.X());
   m_arrow.YGap(y-m_wnd.Y());
//--- Guardamos el puntero del objeto
   CElement::AddToArray(m_arrow);
   return(true);
  }

Vamos a necesitar los métodos para manejar los tamaños (ancho) y el color del elemento de la lista jerárquica ya después de su creación. La configuración del control “Lista jerárquica” está implementada de tal manera que, aparte del área con la propia lista jerárquica, habrá la posibilidad de activar el modo que permite mostrar el área con el contenido del elemento seleccionado a la derecha de su lista jerárquica. En otras palabras, en esta área va a mostrarse la lista de los elementos que forman parte del elemento seleccionado en la lista jerárquica. Al situar el cursor en la raya entre estas áreas, con el botón izquierdo del ratón pulsado al mismo tiempo, se podrá modificar (simultáneamente) el ancho del área de la lista jerárquica y el área de la lista del contenido. 

Al cambiar el ancho de las áreas, van a actualizarse (1) las coordenadas X de los objetos de los elementos de la lista del contenido y (2) el ancho de los objetos de los elementos de ambas listas. Por esta razón, aquí para el texto de los elementos se utiliza el objeto tipo CEdit, en vez del CLabel. Si en este caso se utiliizan los objetos tipo CLabel, entonces cuando va a cambiar los tamaños de las áreas de las listas, Usted se enfrentará con el problema cuando las etiquetas de texto pueden salir fuera de los límites del formulario (ventana).

Para actualizar las coordenadas X, escribiremos el método CTreeItem::UpdateX(). Para establecer la coordenada X, hay que pasarla a este método. En este caso, todos los cálculos van a realizarse en la clase de la lista jerárquica. Ya volveremos a este asunto más tarde. 

class CTreeItem : public CElement
  {
public:
   void              UpdateX(const int x);
  };
//+------------------------------------------------------------------+
//| Actualización de la coordenada X                                          |
//+------------------------------------------------------------------+
void CTreeItem::UpdateX(const int x)
  {
//--- Actualización de las coordenadas generales y del margen desde el punto extremo
   CElement::X(x);
   CElement::XGap(CElement::X()-m_wnd.X());
//--- Coordenadas y el margen del fondo
   m_area.X_Distance(CElement::X());
   m_area.XGap(CElement::X()-m_wnd.X());
//--- Las coordenadas y el margen de la flecha
   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());
//--- Las coordenadas y el margen de la imagen
17
   m_icon.X(l_x);
   m_icon.X_Distance(l_x);
   m_icon.XGap(l_x-m_wnd.X());
//--- Coordenadas y el margen de la etiqueta 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());
  }

Para el cambio del ancho del elemento de la lista, hay que usar el método CTreeItem::UpdateWidth(): 

class CTreeItem : public CElement
  {
public:
   void              UpdateWidth(const int width);
  };
//+------------------------------------------------------------------+
//| Actualización del ancho                                                |
//+------------------------------------------------------------------+
void CTreeItem::UpdateWidth(const int width)
  {
//--- Ancho del fondo
   CElement::XSize(width);
   m_area.XSize(width);
   m_area.X_Size(width);
//--- Ancho de la etiqueta de texto
   int w=CElement::X2()-m_label.X()-1;
   m_label.XSize(w);
   m_label.X_Size(w);
  }

Aparte de la actualización de la coordenada X y del ancho de los elementos, vamos a necesitar el método para la actualización de la coordenada Y. Es que la implementación de ambas listas del control supone durante la implementación del control  (CTreeView) la creación de todos los elementos de la lista de golpe, y no sólo la creación de su cantidad visible, como ha sido hecho en los tipos CListView, CLabelsTable y CTable considerados en los artículos anteriores. Aquí vamos a aplicar la tecnología cuando los elementos que no caben en el área de la lista van a ocultarse. Es decir, en vez de la actualización continua de los valores de varios parámetros de objetos durante el desplazamiento de la lista o reducción/expansión de las listas locales de los elementos, aquí simplemente va a realizarse la gestión de la visibilidad de los elementos. Cuando los elementos innecesarios en este momento es bastante fácil de ocultar, pues en el momento de su demostración es necesario actualizar la coordenada Y. Para eso escribiremos el método CTreeItem::UpdateY() cuyo código va a continuación: 

class CTreeItem : public CElement
  {
public:
   void              UpdateY(const int y);
  };
//+------------------------------------------------------------------+
//| Actualización de la coordenada Y                                          |
//+------------------------------------------------------------------+
void CTreeItem::UpdateY(const int y)
  {
//--- Actualización de las coordenadas generales y del margen desde el punto extremo
   CElement::Y(y);
   CElement::YGap(CElement::Y()-m_wnd.Y());
//--- Coordenadas y el margen del fondo
   m_area.Y_Distance(CElement::Y());
   m_area.YGap(CElement::Y()-m_wnd.Y());
//--- Las coordenadas y el margen de la flecha
   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());
//--- Las coordenadas y el margen de la imagen
   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 y el margen de la etiqueta 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());
  }

La gestión del color de los elementos va a realizarse en la clase de la lista jerárquica (CTreeView). Para eso vamos a necesitar dos métodos:

  • para cambiar el color del elemento respecto al estado indicado;
  • para cambiar el color del objeto al situar el cursor sobre él.

Abajo se puede ver el código de estos métodos. 

class CTreeItem : public CElement
  {
public:
   //--- Cambio del color del elemento respecto al estado indicado
   void              HighlightItemState(const bool state);
   //--- Cambio del color al situar el cursor encima
   void              ChangeObjectsColor(void);
  };
//+------------------------------------------------------------------+
//| Cambio del color del elemento respecto al estado indicado         |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| Cambio del color del objeto al situar el cursor sobre él                    |
//+------------------------------------------------------------------+
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);
     }
  }

A continuación, vamos a analizar la clase cuyo propósito es la creación de los punteros para el cursor del ratón en la interfaz gráfica. 

 


Clase CPointer para la creación del cursor del ratón

El control “Lista jerárquica” se compone de dos áreas. En el área izquierda se encuentra la propia lista jerárquica. En el área derecha se muestra el contenido del elemento seleccionado de la lista jerárquica. Más tarde hablaremos sobre eso con más detalles. Ahora vamos a centrarnos en el hecho de que el tamaño (ancho) de estas áreas se puede cambiar, tal como esta implementado por ejemplo en el explorador del sistema operativo Windows. Al situar el cursor en la raya donde el área de la lista jerárquica y el área de la lista del contenido se lindan, el icono del puntero se cambia por la flecha doble. Vamos a crear la clase (CPointer) con la funcionalidad que permita gestionar la visualización de los punteros para el cursor del ratón. Por ahora no se puede reemplazar el cursor de sistema del ratón a través de los medios MQL en la versión actual del terminal, pero se puede completarlo con una imagen personalizada, lo que también tendrá bastante buena pinta y hará que la interfaz gráfica sea aún más intuitivo y comprensible para el usuario.

La clase CPointer, igual que los demás controles de la librería, va a derivarse de la clase CElement. Hay que incluirla sólo en aquellos controles en los que va a utilizarse. El puntero al formulario no es necesario en esta clase, porque este tipo de objetos es completamente libre y no está vinculado a nada. Tampoco es necesario incluirla en la base común de los controles, y va a controlarse completamente por la clase en la que está incluida.

Creamos el archivo Pointer.mqh con la clase CPointer en la misma carpeta donde se ubican los archivos de la librería. Para la creación del puntero va a utilizarse el objeto tipo CBmpLabel. La clase CBmpLabel se encuentra en el archivo Objects.mqh, cuyo contenido hemos considerado en la primera parte de la serie de esos artículos.

//+------------------------------------------------------------------+
//|                                                      Pointer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
//+------------------------------------------------------------------+
//| Clase para la creación del puntero del cursor del ratón                        |
//+------------------------------------------------------------------+
class CPointer : public CElement
  {
private:
   //--- Objeto para crear el control
   CBmpLabel         m_pointer_bmp;
   /---
public:
                     CPointer(void);
                    ~CPointer(void);
   //---
public:
   //--- Desplazamiento del control
   virtual void      Moving(const int x,const int y);
   //--- (1) Mostrar, (2) ocultar, (3) resetear, (4) eliminar
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CPointer::CPointer(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CPointer::~CPointer(void)
  {
  }

En la versión actual de la librería se puede establecer uno de cuatro tipos de punteros para el cursor. Para el uso cómodo, añadimos la enumeración ENUM_MOUSE_POINTER en el archivo Enums.mqh (véase el código de abajo). Además de cuatro tipos predefinidos, se puede elegir el tipo personalizado (MP_CUSTOM). 

//+------------------------------------------------------------------+
//| Enumeración de tipos de punteros                                    |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_POINTER
  {
   MP_CUSTOM     =0,
   MP_X_RESIZE   =1,
   MP_Y_RESIZE   =2,
   MP_XY1_RESIZE =3,
   MP_XY2_RESIZE =4
  };

Descripción de la enumeraciones del código de arriba:

  • MP_CUSTOM — tipo personalizado.
  • MP_X_RESIZE — cambio de tamaños horizontales.
  • MP_Y_RESIZE — cambio de tamaños verticales.
  • MP_XY1_RESIZE — cambio de tamaños por la diagonal 1.
  • MP_XY2_RESIZE — cambio de tamaños por la diagonal 2.

Antes de la creación del puntero del cursor bastará con establecer su tipo, las imágenes correspondientes se colocarán automáticamente en el objeto gráfico desde el conjunto predefinido en los recursos del control. Puede descargar el archivo con estas imágenes al final del artículo. 

//--- 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"

Si desea establecer sus propias imágenes para el puntero, hay que seleccionar el tipo MP_CUSTOM desde la enumeración ENUM_MOUSE_POINTER y utilizar los métodos CPointer::FileOn() y CPointer::FileOff() para indicar la ruta hacia estas imágenes. 

class CPointer : public CElement
  {
private:
   //--- Imágenes para los punteros
   string            m_file_on;
   string            m_file_off;
   //--- Tipo del puntero
   ENUM_MOUSE_POINTER m_type;
   //---
public:
   //--- Establecer iconos para los punteros
   void              FileOn(const string file_path)       { m_file_on=file_path;         }
   void              FileOff(const string file_path)      { m_file_off=file_path;        }
   //--- Devolver y establecer el tipo del puntero
   ENUM_MOUSE_POINTER Type(void)                    const { return(m_type);              }
   void              Type(ENUM_MOUSE_POINTER type)        { m_type=type;                 }
  };

Además, aquí vamos a necesitar los métodos para la actualización de las coordenadas y devolución/establecimiento del estado del puntero

class CPointer : public CElement
  {
public:
   //--- Devolución y establecimiento del estado del puntero
   bool              State(void)                    const { return(m_pointer_bmp.State()); }
   void              State(const bool state)              { m_pointer_bmp.State(state);    }
   //--- Actualización de las coordenadas
   void              UpdateX(const int x)                 { m_pointer_bmp.X_Distance(x);   }
   void              UpdateY(const int y)                 { m_pointer_bmp.Y_Distance(y);   }
  };

Por defecto, en el constructor de la clase está establecido el tipo MP_X_RESIZE para el puntero: 

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CPointer::CPointer(void) : m_file_on(""),
                           m_file_off(""),
                           m_type(MP_X_RESIZE)
  {
  }

Necesitamos el método en el que antes de la creación del control van a determinarse las imágenes para el puntero en función del tipo establecido. Para eso escribiremos el método CPointer::SetPointerBmp(). En caso si ha sido seleccionado el tipo (MP_CUSTOM) y las rutas hacia las imágenes no han sido especificadas, en el registro aparecerá un mensaje sobre ello.  

class CPointer : public CElement
  {
private:
   //--- Establecer las imágenes para el puntero del cursor
   void              SetPointerBmp(void);
  };
//+------------------------------------------------------------------+
//| Establecer las imágenes para el puntero según el tipo del puntero               |
//+------------------------------------------------------------------+
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;
     }
//--- Si ha sido especificado el tipo personalizado (MP_CUSTOM)
   if(m_file_on=="" || m_file_off=="")
      ::Print(__FUNCTION__," > Para el puntero del cursor hay que establecer ambas imágenes");
  }

Para la creación del control necesitamos sólo un método público CPointer::CreatePointer(), su código se muestra a continuación. En la formación del nombre del objeto gráfico va a participar el índice del control, cuyo valor se establece por defecto en el constructor de la clase CPointer y es igual a cero. Es necesario para que haya posibilidad de crear varios objetos tipo CPointer para diferentes propósitos dentro del mismo control al que va a adjuntarse el puntero. 

class CPointer : public CElement
  {
public:
   //--- Crea el icono para el puntero
   bool              CreatePointer(const long chart_id,const int subwin);
  };
//+------------------------------------------------------------------+
//| Crea el puntero                                                |
//+------------------------------------------------------------------+
bool CPointer::CreatePointer(const long chart_id,const int subwin)
  {
//--- Formación del nombre del objeto
   string name=CElement::ProgramName()+"_pointer_bmp_"+(string)CElement::Index()+"__"+(string)CElement::Id();
//--- Establecer las imágenes para el puntero
   SetPointerBmp();
//--- Creación del objeto
   if(!m_pointer_bmp.Create(m_chart_id,name,m_subwin,0,0))
      return(false);
//--- Establecemos las propiedades
   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");
//--- Ocultar el objeto
   m_pointer_bmp.Timeframes(OBJ_NO_PERIODS);
   return(true);
  }

Pues, tenemos todos los controles para crear la lista jerárquica y a continuación vamos a ver cómo está organizada la clase CTreeView para su creación. 

 


Desarrollo de la clase CTreeView para la creación de la lista jerárquica

Creamos el archivo TreeView.mqh con la clase CTreeView con los métodos estándar, tal como lo hemos hecho para todos los controles, y lo incluimos en el archivo WndContainer.mqh:

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "TreeView.mqh"

Vamos a nombrar los componentes de este control que van a formar la lista jerárquica.

  1. Fondo de la lista jerárquica
  2. Lista de elementos de la lista jerárquica
  3. Barra de desplazamiento vertical de la lista jerárquica
  4. Fondo de la lista del contenido
  5. Lista de elementos de la lista del contenido
  6. Barra de desplazamiento vertical de la lista del contenido
  7. Puntero del cursor del ratón para seguir cómo se cambia el ancho del área de la lista jerárquica y del área del contenido



 

Fig. 4. Partes integrantes del control “Lista jerárquica”.

La imagen para el puntero del cursor será ocultada después de la creación. Va a aparecer sólo cuando el cursor se sitúa sobre el espacio estrecho donde lindan las áreas de las listas.

Para crear el control “Lista jerárquica”, vamos a necesitar siete métodos privados (private) y un método público (public): 

class CTreeView : public CElement
  {
private:
   //--- Objetos para crear el control
   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 la creación de la lista jerá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 ejemplo, mostraremos el código sólo de uno de ellos: el método CTreeView::CreateXResizePointer() para la creación del puntero del cursor del ratón (véase el código de abajo). Aquí hay que prestar atención en los siguientes detalles:

  • El puntero del cursor no va a crearse si una de las siguientes condiciones es verdadera:
    • el modo que permite cambiar el ancho de las listas no está activado. 
    • el modo de los elementos-pestañas está activado.
  • Aquí los márgenes del puntero van a calcularse desde el cursor de sistema del ratón (este tipo del control no se vincula a la ventana como los demás controles).
  • Si necesitamos varios punteros en el control, pues para cada uno de ellos hay que establecer su índice del control. Puesto que en la versión actual de la lista jerárquica va a utilizarse sólo un puntero, se puede omitir esta propiedad, porque por defecto el puntero se inicializa con el valor 0 en el contructor.
  • Es obligatorio indicar el identificador del control al que se adjunta el puntero. Es necesario para evitar conflictos de los nombres de objetos gráficos de diferentes controles, donde pueden usarse los punteros del cursor. 
//+------------------------------------------------------------------+
//| Crea el puntero del cursor para cambiar el ancho                      |
//+------------------------------------------------------------------+
bool CTreeView::CreateXResizePointer(void)
  {
//--- Salir si no es necesario cambiar el ancho del área del contenido o
//    el el modo de los elementos-pestañas está activado
   if(!m_resize_content_area_mode || m_tab_items_mode)
      return(true);
//--- Establecemos las propiedades
   m_x_resize.XGap(12);
   m_x_resize.YGap(9);
   m_x_resize.Id(CElement::Id());
   m_x_resize.Type(MP_X_RESIZE);
//--- Creación del control
   if(!m_x_resize.CreatePointer(m_chart_id,m_subwin))
      return(false);
//---
   return(true);
  }

Antes de empezar a describir el resto de los métodos de la clase CTreeView, vamos a aclarar qué funcionalidad necesitamos tener en el control “Lista jerárquica”. Implementamos la clase para la creación de este control de tal manera que luego haya posibilidad de crear las listas jerárquicas aplicables para diferentes fines. 

Por ejemplo, hay que tomar en cuenta desde el principio algunas particularidades durante el desarrollo de futuras clases para la creación de exploradores de archivos. Fíjese como está organizado el administrador de archivos en el explorador del sistema operativo Windows. Si la visualización de la lista jerárquica está activada en la parte izquierda de la ventana (en Windows se llama “Zona de transición”), en esta lista Usted no verá los elementos que son archivos, aparecen sólo las carpetas. Todos los archivos del explorador del sistema se muestran sólo en el área del contenido (véase la captura de pantalla de abajo).


Fig. 5. Administrador de archivos en el explorador de Windows 7. A la izquierda - lista jerárquica. A la derecha - área del contenido.

Pero a menudo surge la necesidad de mostrar en la lista jerárquica no sólo las carpetas, sino también los archivos. Por ejemplo, se puede hacer que cuando un archivo se selecciona en la lista jerárquica, su contenido se muestra en el área derecha. Por eso, si la clase del control CTreeView se utiliza para la creación del explorador de archivos, hay que ofrecer al usuario de la librería dos modos opcionales: (1) mostrar en la lista jerárquica las carpetas y los archivos o (2) sólo las crapetas. Para eso insertamos la enumeración ENUM_FILE_NAVIGATOR_MODE en el archivo Enums.mqh (véase el código de abajo).

//+------------------------------------------------------------------+
//| Enumeración de los modos en el explorador de archivos                        |
//+------------------------------------------------------------------+
enum ENUM_FILE_NAVIGATOR_MODE
  {
   FN_ALL          =0,
   FN_ONLY_FOLDERS =1
  };

No siempre es necesario que el contenido del elemento se muestre en el área del contenido, por eso preveremos la posibilidad de desactivar su visualización. Por ejemplo, eso podría ser útil cuando la lista jerárquica se utiliza como pestañas a las que se adjuntan los grupos de controles de la librería, lo mismo que ha sido demostrado en el artículo Interfaces gráficas VII: Control “Pestañas” (Capítulo 2)

Además de eso, vamos a asegurar otros modos adicionales para la configuración más precisa del control “Lista jerárquica”. Abajo se listan los modos de la versión actual:

  • Modo del explorador de archivos
  • Modo del resalto al apuntar con el cursor
  • Modo de visualización del contenido del elemento en el área del contenido
  • Modo del cambio del ancho del área del contenido
  • Modo de los elementos-pestañas

Para la configuración de los modos del elemento, antes de su creación es necesario usar los métodos que se muestran en el código de abajo:

class CTreeView : public CElement
  {
private:
   //--- Modo del explorador de archivos
   ENUM_FILE_NAVIGATOR_MODE m_file_navigator_mode;
   //--- Modo del resalto al apuntar con el cursor
   bool              m_lights_hover;
   //--- Modo de visualización del contenido del elemento en el área del contenido
   bool              m_show_item_content;
   //--- Modo del cambio del ancho de las listas
   bool              m_resize_list_area_mode;
   //--- Modo de los elementos-pestañas
   bool              m_tab_items_mode;
   //---
public:
   //--- (1) Modo del explorador de archivos, (2) modo del resalto al apuntar con el cursor, 
   //    (3) modo de visualización del contenido del elemento, (4) modo del cambio del ancho de las listas, (5) modo de los elementos-pestañas
   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;                       }
  };

Abajo se listan las propiedades que estarán disponibles para configurar la apariencia del control.

  • Ancho del área de la lista jerárquica
  • Color del fondo
  • Color del marco del fondo
  • Ancho del área del contenido
  • Alto de los elementos en las listas
  • Color del fondo de los elementos en diferentes estados
  • Colores del texto de los elementos en diferentes estados
  • Imágenes para el indicio de la presencia del contenido en el elemento (por defecto, es la imagen en forma de la flecha derecha)
class CTreeView : public CElement
  {
private:
   //--- Ancho del área de la lista jerárquica
   int               m_treeview_area_width;
   //--- Color del fondo y del marco del fondo
   color             m_area_color;
   color             m_area_border_color;
   //--- Ancho del área del contenido
   int               m_content_area_width;
   //--- Alto de los elementos
   int               m_item_y_size;
   //--- Colores de los elementos en diferentes estados
   color             m_item_back_color_hover;
   color             m_item_back_color_selected;
   //--- Colores del texto en estados diferentes
   color             m_item_text_color;
   color             m_item_text_color_hover;
   color             m_item_text_color_selected;
   //--- Imágenes para las flechas
   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) Alto del elemento, (2) ancho de la lista jerárquica y (3) lista del contenido
   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;                  }
   //--- Color del fondo y del marco del fondo del elemento
   void              AreaBackColor(const color clr)                     { m_area_color=clr;                             }
   void              AreaBorderColor(const color clr)                   { m_area_border_color=clr;                      }
   //--- Colores de los elementos en diferentes estados
   void              ItemBackColorHover(const color clr)                { m_item_back_color_hover=clr;                  }
   void              ItemBackColorSelected(const color clr)             { m_item_back_color_selected=clr;               }
   //--- Colores del texto en estados diferentes
   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;               }
   //--- Imágenes para la flecha del elemento
   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;     }
  };

Necesitaremos los campos para almacenar los índices de los elementos seleccionados en la lista jerárquica y en la lista del contenido. Antes de la creación de la lista jerárquica, se puede usar el método CTreeView::SelectedItemIndex() para indicar el elemento que debe quedarse seleccionado después de la creación. 

class CTreeView : public CElement
  {
private:
   //--- Índices de los elementos seleccionados en las listas
   int               m_selected_item_index;
   int               m_selected_content_item_index;
   //---
public:
   //--- (1) Selecciona el elemento por el índice y (2) devuelve el índice del elemento seleccionado
   void              SelectedItemIndex(const int index)                 { m_selected_item_index=index;              }
   int               SelectedItemIndex(void)                      const { return(m_selected_item_index);            }
  };

 


Parámetros para la formación de las listas del control

Antes de crear el control tipo CTreeView, primero hay que formar la lista jerárquica. Para eso necesitaremos el método para añadir a la lista los elementos con los parámetros que permitirán al programa comprender luego en qué orden hay que organizar los elementos en el proceso del uso de la lista. Sin algunos de estos parámetros será imposible organizar correctamente la secuencia jerárquica. Abajo se muestra la lista completa de estos parámetros.

  • Índice general en la lista

Todos los elementos de la lista jerárquica se colocan sucesivamente en el ciclo, y a cada uno de ellos se le asigna el índice de la iteración actual. Durante el uso del elemento, independientemente de hasta qué grado está expandida la lista jerárquica ante el usuario en este momento, el índice general de la lista para cada elemento se queda inalterable hasta el final de su “vida”.

Por ejemplo, para los elementos A, B, C, D, E y F, los índices generales serán 0, 1, 2, 3, 4 y 5, respectivamente. Supongamos que los elementos B y C se encuentran en el elemento A, y el elemento E se encuentra en el elemento D. Para que sea más claro, en la imagen de abajo se muestran dos variantes del estado de la lista jerárquica. En la parte izquierda (1) se muestra la lista completamente expandida y se ve la secuencia interrumpida de su indexación. En la parte derecha de la imagen (2) se muestra la versión cuando las listas de los elementos A y D, que incluyen los elementos B, C y E, están plegadas, guardando al mismo tiempo los índices iniciales que les han sido asignados en el momento de la formación e inicialización de los arrays de la lista jerárquica.

 

Fig. 6. A la izquierda 1) – lista completamente desplegada. A la derecha 2) – lista plegada.

  • Índice general de la lista del nodo anterior

Por ejemplo, los elementos de la carpeta raíz no tienen ningún nodo anterior, por eso el valor de este parámetro será - 1. Si el elemento tiene contenido (lista local de elementos), todos sus elementos reciben el índice general de la lista del nodo en el que se encuentran. 

En la imagen se observa que los elementos A, D y H reciben el valor -1. A los elementos B y C les ha sido asignado el índice de la lista [0] del nodo anterior A, y los elementos E, F y G tienen el ´´indice general de la lista [3] del nodo anterior D.

 

Fig. 7. A todos los elementos hijos del nodo se les asigna su índice general de la lista.

  • Descripción del elemento (texto a mostrar en el elemento)

Por ejemplo, pueden ser los nombres de las carpetas y archivos (si se crea el explorador de archivos), los nombres de las categorías y subcategorías, algunos grupos de controles (si se crea el control “Pestañas”).


  • Número del nivel del nodo

La numeración se empieza desde cero. Es decir, los elementos en la carpeta raíz tienen el valor 0. Luego, según vaya aumentando el nivel de anidación, el valor para el elemento anidado se aumenta a uno. Para comprenderlo mejor, observe la imagen de abajo. En la escala horizontal de arriba, a las columnas de los elementos les han sido asignados los números de sus niveles. El nivel de los nodos A, D y J tiene el valor 0. Para los elementos B, C, E, I y K es el número 1, y para los elementos F, G y H es el número 2.


 

Fig. 8. El número del nodo está vinculado con el nivel de su anidación.


  • Índice local

Si el elemento tiene el contenido (su propia lista de elementos), esta lista va a tener su propia indexación que empieza con el valor cero. En el esquema de abajo, los índices de las listas locales se muestran con cifras azules.

 

Fig. 9. Indexación de listas locales.


  • Índice local del nodo anterior

Aquí se utiliza el mismo principio como en el caso del parámetro del índice general del nodo anterior. Los elementos de la carpeta raíz no tienen ningún nodo anterior, por eso el valor de este parámetro para estos elementos será -1. Los elementos que tienen nodo anterior reciben su índice local. En la imagen de abajo, los índices locales del nodo anterior se muestran en rojo.

 

Fig. 10 Índices locales del nodo anterior.


  • Número de elementos (tamaño del array de la lista local del nodo). 

Este valor depende del tipo (de la enumeración ENUM_TYPE_TREE_ITEM) que será asignado al elemento en el campo de la clase CTreeItem. En el esquema de abajo, el número de los elementos en todas las listas locales está representado en forma numérica. Se observa que en los elementos A, D, E y J hay contenido (2, 2, 3 y 1, respectivamente), los elementos B, C, F, G, H, I y K están vacíos (0).

 

Fig. 11. Numero de elementos en las listas locales de los nodos.

  • Estado del elemento. 

Expandido/reducido, en caso de la presencia de la lista local (contenido). Permite inmediatamente después de la creación de la lista jerárquica especificar qué elementos deben estar expandidos.

Como ejemplo, se puede mostrar todos los parámetros clave para la identificación del elemento de la lista jerárquica en una tabla sinóptica (véase el ejemplo en la imagen de abajo). Para una interpretación unívoca, los valores de todos los parámetros se muestran con colores diferentes. 


 

Fig. 12 Tabla sinóptica de parámetros clave para identificación de los elementos en la lista jerárquica.

Durante el desarrollo de la clase para la creación del explorador de archivos, todos estos parámetros deben calcularse automáticamente, leyendo la estructura jerárquica del directorio general y local de archivos del terminal. Todos eso será considerado detalladamente en el siguiente capítulo de la octava parte de la serie, pero hablaremos de algunos momentos ahora mismo para demostrar los métodos adaptados de la clase de la lista jerárquica (CTreeView) para la creación de los exploradores de archivos. Por ejemplo, aparte del número de los elementos en la lista local del nodo, en caso si se crea el explorador de archivos, hay que indicar también el número de elementos que son carpetas. Además, para cada elemento es necesario el parámetro que permite determinar si se trata de una carpeta o no. Es que en el contexto del explorador de archivos, la ausencia de la lista local en el elemento no significa que es un archivo.

Mientras que durante la creación de una lista jerárquica común, es necesario formar su estructura personalmente. Pero en cualquier caso, en ambas ocasiones va a utilizarse el mismo método CTreeView::AddItem(), que permite añadir el elemento a la lista jerárquica con los parámetros especificados que deben pasarse como argumentos. A continuación, se muestra el código de este método y la lista de los arrays en los que van a almacenarse todos los parámetros de los elementos. Preste atención que se puede establecer la imagen única para cada elemento.

class CTreeView : public CElement
  {
private:
   //--- Arrays para todos los elementos de la lista jerárquica (lista general)
   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:
   //--- Añade el elemento a la lista jerá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);
  };
//+------------------------------------------------------------------+
//| Añade un elemento al array general de la lista jerá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)
  {
//--- Aumentamos el tamaño del array a un 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);
//--- Guardamos los valores de los parámetros pasados
   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 la creación del elemento existe la posibilidad de determinar sus parámetros y establecer el ancho inicial para el área de la lista jerárquica y el área del contenido. Por defecto, el valor en el campo de la clase donde se establece el ancho del área del contenido está inicializado con el valor  WRONG_VALUE  (véase el código de abajo). Eso significa que si no establecemos el ancho para el área del contenido, no serán creadas tres partes integrantes del elemento: (1) fondo del área del contenido, (2) arrays de los elementos para esta área y (3) la barra de desplazamiento para la lista en esta área. Eso no ocurrirá incluso si en los parámetros se establece el modo “Mostrar el contenido del elemento seleccionado en la lista jerárquica”. Es decir, se puede desactivar la visualización de la lista dejando el fondo, pero no se puede hacer al revés.

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
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)
  {
//--- Guardamos el nombre de la clase del control en la clase base
   CElement::ClassName(CLASS_NAME);
//--- Establecemos las prioridades para el clic izquierdo del ratón
   m_zorder=0;
  }

Para la lista de los elementos en el área del contenido vamos a necesitar una lista separada de los arrays dinámicos. No es tan grande como para la lista jerárquica, es que aquí no hace falta seguir la secuencia jerárquica. Para la formación de esta lista vamos a necesitar sólo tres parámetros.

  • Índice general de la lista del área del contenido
  • Índice general de la lista jerárquica
  • Descripción del elemento (texto a mostrar)
class CTreeView : public CElement
  {
private:
   //--- Arrays para la lista del contenido de elementos seleccionados en la lista jerárquica (lista completa)
   int               m_c_list_index[];
   int               m_c_tree_list_index[];
   string            m_c_item_text[];
  };

La fijación de los tamaños e inicialización de estos arrays va a realizarse en el método de la creación de la lista del contenido CTreeView::CreateContentItems(). En los arrays entrarán todos los elementos a excepción de los que pertenecen la carpeta raíz, porque no disponen del nodo superior que se puede seleccionar en la lista jerárquica. Aquí mismo, al principio del método, se puede ver la comprobación de los modos establecidos de los que depende la creación del área del contenido.

//+------------------------------------------------------------------+
//| Crea la lista del contenido del elemento seleccionado                     |
//+------------------------------------------------------------------+
bool CTreeView::CreateContentItems(void)
  {
//--- Salir si no es necesario mostrar el contenido del elemento o
//    si el área del contenido está desactivada
   if(!m_show_item_content || m_content_area_width<0)
      return(true);
//--- Coordenadas y el ancho
   int x =m_content_area.X()+1;
   int y =CElement::Y()+1;
   int w =m_content_area.X2()-x-1;
//--- Contador del número de elementos
   int c=0;
//--- 
   int items_total=::ArraySize(m_items);
   for(int i=0; i<items_total; i++)
     {
      //--- En esta lista no deben entrar los elementos de la carpeta raíz, 
      //    por eso si el nivel del nodo es menos 1, pasamos al siguiente
      if(m_t_node_level[i]<1)
         continue;
      //--- Aumentar el tamaño de los arrays a un 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 de la coordenada Y
      y=(c>0)? y+m_item_y_size-1 : y;
      //--- Pasar el objeto del panel
      m_content_items[c].WindowPointer(m_wnd);
      //--- Establecemos las propiedades antes de la creación
      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);
      //--- Márgenes desde el punto extremo del panel
      m_content_items[c].XGap(x-m_wnd.X());
      m_content_items[c].YGap(y-m_wnd.Y());
      //--- Creación del 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);
      //--- Ocultar el elemento
      m_content_items[c].Hide();
      //--- El elemento será desplegable
      m_content_items[c].IsDropdown(true);
       //--- Guardar (1) el índice de la lista general del contenido, (2) índice de la lista jerárquica y (3) el texto del contenido
      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++;
     }
//--- Guardar el tamaño de la lista
   m_content_items_total=::ArraySize(m_content_items);
   return(true);
  }

Los arrays dinámicos para la lista jerárquica y la lista en el área del contenido que hemos considerado antes sirven para almacenar las listas completas. Pero dado que los elementos de estas listas no siempre van a mostrarse a la vez, necesitamos otros dos grupos de los arrays dinámicos que van a reformarse constantemente durante la interacción con las lista jerárquica. Va a tomarse en cuenta el estado actual de los nodos que disponen de las listas locales que pueden estar expandidas o reducidas. Vamos a ver un ejemplo más detallado.

Supongamos que tenemos una lista jerárquica de 11 elementos:

 Fig. 13. Modelo de la lista jerárquica de 11 elementos.

Fig. 13 Modelo de la lista jerárquica de 11 elementos.

Dado que los elementos A, D y J se encuentran en la lista de la carpeta raíz, no van a entrar en la lista completa de elementos para el área del contenido. En la imagen de abajo, a la derecha (1) se muestra la lista de todos los elementos de la lista jerárquica. Los arrays dinámicos para el almacenamiento de los parámetros de esta lista, que han sido analizados en uno de los códigos de arriba, contienen el prefijo ‘t’ (abreviatura de la palabra ‘tree’) en sus nombres. A la izquierda (2) se muestra la lista de los elementos que han entrado en el área del contenido. Los arrays dinámicos para el almacenamiento de los parámetros de esta lista contienen el prefijo ‘c’ (abreviatura de la palabra ‘content’) en sus nombres.

 Fig. 14. Listas completas para ambos grupos.

Fig. 14 Listas completas para ambos grupos.

En este ejemplo, las listas locales se encuentran en los elementos A, D, E y J. En la imagen de abajo, están marcadas con el color azul. 

 Fig. 15. Elementos que contienen listas locales (marcadas con el color azul).

Fig. 15 Elementos que contienen listas locales (marcadas con el color azul).

Cada vez cuando en la lista jerárquica se seleccione un elemento que incluye el contenido, la lista en el área del contendido va a volver a formarse. Por ejemplo, si seleccionamos el elemento A, en el área del contenido va a formarse la lista de los elementos B y C (versión 1 en la imagen). Si seleccionamos el elemento D, va a formarse la lista de los elementos E e I (versión 2 en la imagen).

 Fig. 16. Ejemplo de formación de las listas para el área del contenido.

Fig. 16. Ejemplo de formación de las listas para el área del contenido.

La lista de los elementos mostrados en la lista jerárquica vuelve a formarse en el momento de la expansión y reducción de las listas locales de los nodos. Aquí todo está muy claro y es evidente. Los elementos de las listas reducidas no se muestran y no serán incluidos en los arrays para el almacenamiento de los elementos mostrados de la lista jerárquica.

Para todo lo arriba mencionado, vamos a necesitar un array para la lista de los elementos mostrados en la lista jerárquica que va a almacenar los índices generales de la lista jerárquica, y tres arrays para la lista del contenido donde van a guardarse (1) los índices generales de la lista del contenido, (2) índices generales de la lista jerárquica y (3) la descripción (texto a mostrar) de los elementos: 

class CTreeView : public CElement
  {
private:
   //--- Arrays para la lista de los elementos mostrados de la lista jerárquica
   int               m_td_list_index[];
   //--- Arrays para la lista de los elementos mostrados de la lista del contenido
   int               m_cd_list_index[];
   int               m_cd_tree_list_index[];
   string            m_cd_item_text[];
  };

A continuación, vamos a ver los métodos que se usan para formar y manejar las listas según los algoritmos descritos anteriormente. 

 


Métodos para gestionar las listas del control

Después de pasar todos los parámetros a la clase CTreeView y crear el control, es necesario formar y actualizar las listas de los elementos que van a mostrarse. Para reducir el código de los métodos principales, crearemos los campos y los métodos privados adicionales que van a utilizarse para los fines de servicio. Vamos a necesitar los valores del nivel mínimo y máximo de los nodos y el número de lo elementos en la carpeta raíz de la lista jerárquica

class CTreeView : public CElement
  {
private:
   //--- Nivel (1) mínimo y (2) máximo del nodo
   int               m_min_node_level;
   int               m_max_node_level;
  //--- Número de elementos en la carpeta raíz
   int               m_root_items_total;
   //---
private:
   //--- Determinar y establecer (1) límites de los nodos y (2) tamaño de la carpeta raíz
   void              SetNodeLevelBoundaries(void);
   void              SetRootItemsTotal(void);
  };
//+------------------------------------------------------------------+
//| Determinar y establecer límites de los nodos                             |
//+------------------------------------------------------------------+
void CTreeView::SetNodeLevelBoundaries(void)
  {
//--- Determinar el nivel mínimo y máximo de los nodos
   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)];
  }
//+------------------------------------------------------------------+
//| Determinar y establecer el tamaño de la carpeta raíz               |
//+------------------------------------------------------------------+
void CTreeView::SetRootItemsTotal(void)
  {
//--- Determinar el número de elementos en la carpeta raíz
   int items_total=::ArraySize(m_items);
   for(int i=0; i<items_total; i++)
     {
      //--- Si es el nivel mínimo, aumentamos el contador
      if(m_t_node_level[i]==m_min_node_level)
         m_root_items_total++;
     }
  }

Antes ya hemos analizado los arrays para la formación de las listas de los elementos a mostrar. Necesitaremos el método CTreeView::AddDisplayedTreeItem() que permitirá llenar este array para la lista jerarquica. Para añadir un elemento a esta lista, hay que pasar el índice general de la lista al método.

class CTreeView : public CElement
  {
private:
   //--- Añade el elemento a la lista en el área del contenido
   void              AddDisplayedTreeItem(const int list_index);
  };
//+------------------------------------------------------------------+
//| Añade el elemento al array de los elementos a mostrar                    |
//| en la lista jerárquica                                             |
//+------------------------------------------------------------------+
void CTreeView::AddDisplayedTreeItem(const int list_index)
  {
//--- Aumentamos el tamaño del array a un elemento
   int array_size=::ArraySize(m_td_list_index);
   ::ArrayResize(m_td_list_index,array_size+1);
//--- Guardamos los valores de los parámetros pasados
   m_td_list_index[array_size]=list_index;
  }

Una vez formado el array, hay que recolocar los elementos y redibujar el control, para mostrar los últimos cambios realizados. Para estos fines crearemos el método auxiliar CTreeView::RedrawTreeList(). En el inicio de este método, el control se oculta. Luego, (1) se calcula la coordenada Y para el primer elemento, (2) se corrige el tamaño del deslizador de la barra de desplazamiento y (3) se calcula el ancho de los elementos tomando en cuenta la presencia/ausencia de la barra de desplazamiento. Luego en el ciclo, con cada iteración para cada elemento, se calcula la coordenada Y y se realiza la actualización de las propiedades. Una vez finalizado el ciclo, el control vuelve a ser visible. 

class CTreeView : public CElement
  {
private:
   //--- Redibujar la lista jerárquica
   void              RedrawTreeList(void);
  };
//+------------------------------------------------------------------+
//| Redibujar el control                                             |
//+------------------------------------------------------------------+
void CTreeView::RedrawTreeList(void)
  {
//--- Ocultar control
   Hide();
//--- Coordenada Y del primer elemento de la lista jerárquica
   int y=CElement::Y()+1;
//--- Obtenemos el número de elementos
   m_items_total=::ArraySize(m_td_list_index);
//--- Corregimos el tamaño de la barra de desplazamiento
   m_scrollv.ChangeThumbSize(m_items_total,m_visible_items_total);
//--- Cálculo del ancho de los elementos de la lista jerárquica
   int w=(m_items_total>m_visible_items_total) ? CElement::XSize()-m_scrollv.ScrollWidth() : CElement::XSize()-2;
//--- Establecemos nuevos valores
   for(int i=0; i<m_items_total; i++)
     {
      //--- Cálculo de la coordenada Y para cada elemento
      y=(i>0)? y+m_item_y_size-1 : y;
      //--- Obtenemos el índice general en la lista
      int li=m_td_list_index[i];
      //--- Actualizamos las coordenadas y el tamaño
      m_items[li].UpdateY(y);
      m_items[li].UpdateWidth(w);
     }
//--- Mostrar el control
   Show();
  }

Todos los campos y los métodos arriba mencionados van a invocarse en el método CTreeView::UpdateTreeViewList(), donde va a formarse y actualizarse la lista jerárquica (véase el código de abajo). Analizaremos este método más detalladamente. 

Aquí necesitaremos cuatro arrays dinámicos locales que permitirán controlar la secuencia de los elementos durante el proceso de formación de la lista. Serán los arrays para:

  • los índices generales de la lista de nodos anteriores;
  • índices locales de los elementos;
  • el número de elementos en el nodo;
  • el número de las carpetas en el nodo.

El tamaño inicial del array tendrá dos elementos más que el número máximo de los nodos en la lista jerárquica. Es necesario para que los valores del elemento actual se guarden (en el ciclo) en el siguiente control de los arrays, y durante la siguiente iteración se comprueben los valores del elemento anterior de acuerdo con el índice actual del ciclo. Originalmente, los arrays se inicializan con el valor -1. Además, preste atención que cada vez cuando el programa entre en este método, el búfer del array m_td_list_index[], que sirve para la formación de la lista jerárquica, se libera y al array se le establece el tamaño cero. También vamos a necesitar el contador de elementos adicional (ii) y la variable (end_list) para establecer la bandera del último elemento en la carpeta raíz. 

Después de la declaración de todos los arrays locales y las variables, el ciclo principal del método CTreeView::UpdateTreeViewList() empieza su trabajo. Va a trabajar hasta que:

  • el contador de los nodos (nl) no supere el máximo establecido;
  • no lleguemos al último elemento en la carpeta raíz (después de comprobar todos sus elementos incluidos);
  • el usuario no elimine el programa. 

Es un ciclo doble. En su primer nivel, se calculan los nodos de la lista jerárquica. En el segundo nivel, se comprueban todos los elementos de la lista local del nodo actual uno por uno. Al principio del segundo ciclo se comprueba el modo del explorador de archivos, por si la lista jerárquica se utiliza para este propósito. Si el modo “Mostrar sólo carpetas en la lista jerárquica” está activado, luego hay que comprobar si el elemento actual es una carpeta, y si no es así, el programa pasa al siguiente elemento. 

Si estas condiciones han sido superadas con éxito, a continuación hay tres comprobaciones más. El programa pasará al siguiente elemento en tres ocasiones descritas a continuación.

  • Si los nodos no coinciden.
  • La secuencia de los índices locales de los elementos no se cumple.
  • Si ahora no nos encontramos en la carpeta raíz y el índice general de la lista del nodo anterior no es igual al análogo en la memoria.

Si hemos superado todas estas comprobaciones, guardamos el índice local del elemento si el siguiente no será menos que el tamaño de la lista local. 

Luego, si resulta que el elemento contiene una lista local y ahora está desplegada, añadimos el elemento al array de los elementos a mostrar en la lista jerárquica. Aquí también hay que guardar los valores actuales del elemento en los arrays locales del método. La única excepción aquí es el índice local del elemento. Hay que guardarlo en el índice del nodo actual (nl), y los valores de los demás parámetros se guardan en el índice del nodo siguiente (n). Además de eso, aquí se pone a cero el contador de los índices locales de los elementos y se detiene el ciclo actual (el segundo) para ir al siguiente nodo.

Si el paso al siguiente nodo no ha ocurrido, eso significa que ha sido un elemento sin lista, o la lista se encuentra plegada en este momento. En este caso, aquí primero también se añade el elemento al array de los elementos a mostrar en la lista jerárquica. Luego, se aumenta el contador de los índices locales de los elementos. 

Si ahora nos encontramos en la carpeta raíz y ya hemos llegado hasta el último elemento de la lista, eso significa que la formación de la lista ha terminado. Se coloca la bandera y el ciclo se detiene. Si todavía no hemos llegado hasta el último elemento en la carpeta raíz, obtenemos el número de los elementos en el nodo actual, o el número de las carpetas si el modo correspondiente está establecido. Si no es el último elemento en la lista actual, vamos al siguiente. Si hemos llegado al último elemento, ahora tenemos que ir al nodo anterior y continuar el ciclo desde el elemento que ha sido comprobado en su lista la última vez. Y así, hasta que no lleguemos al último elemento en la carpeta raíz.

Después de finalizar la formación de la lista jerárquica, al final del método se realiza el redibujo del control.

class CTreeView : public CElement
  {
private:
   //--- Actualiza la lista jerárquica
   void              UpdateTreeViewList(void);
  };
//+------------------------------------------------------------------+
//| Actualiza la lista jerárquica                                     |
//+------------------------------------------------------------------+
void CTreeView::UpdateTreeViewList(void)
  {
//--- Arrays para controlar la secuencia de los elementos:
   int l_prev_node_list_index[]; // índice general de la lista del nodo anterior
   int l_item_index[];           // índice local del elemento
   int l_items_total[];          // número de elementos en el nodo
   int l_folders_total[];        // número de las carpetas en el nodo
//--- Establecemos el tamaño inicial de los 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);
//--- Inicialización de arrays
   ::ArrayInitialize(l_prev_node_list_index,-1);
   ::ArrayInitialize(l_item_index,-1);
   ::ArrayInitialize(l_items_total,-1);
   ::ArrayInitialize(l_folders_total,-1);
//--- Liberamos el array de los elementos mostrados de la lista jerárquica
   ::ArrayFree(m_td_list_index);
//--- Contador de los índices locales de los elementos
   int ii=0;
//--- Para establecer la bandera del último elemento en la carpeta raíz
   bool end_list=false;
//--- reunimos los elementos a mostrar en el array. El ciclo va a trabajar hasta que:
//    1: el contador de los nodos no supere el máximo;
//    2: no lleguemos al último elemento (después de comprobar todos sus elementos incluidos);
//    3: el usuario no elimine el 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++)
        {
        //--- Si el modo “Mostrar sólo carpetas” está activado
         if(m_file_navigator_mode==FN_ONLY_FOLDERS)
           {
            //--- Si es un archivo, ir al siguiente elemento
            if(!m_t_is_folder[i])
               continue;
           }
         //--- Si (1) no es nuestro nodo o (2) la secuencia de los índices locales de los elementos no se cumple,
            //--- ir al siguiente
         if(nl!=m_t_node_level[i] || m_t_item_index[i]<=l_item_index[nl])
            continue;
         //--- Ir al siguiente elemento si ahora no estamos en la carpeta raíz y 
         //    el índice general de la lista del nodo anterior no es igual al análogo en la memoria
         if(nl>m_min_node_level && m_t_prev_node_list_index[i]!=l_prev_node_list_index[nl])
            continue;
         //--- Guardamos el índice local del elemento si el siguiente no será menos que el tamaño de la lista local
         if(m_t_item_index[i]+1>=l_items_total[nl])
            ii=m_t_item_index[i];
         //--- Si la lista del elemento actual está desplegada
         if(m_t_item_state[i])
           {
            //--- Añadimos el elemento al array de los elementos a mostrar en la lista jerárquica
            AddDisplayedTreeItem(i);
            //--- Guardamos el valor actual y vamos al nodo siguiente
            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];
            //--- Ponemos a cero el contador de los índices locales de los elementos
            ii=0;
    //--- ir al siguiente nodo
            break;
           }
         //--- Añadimos el elemento al array de los elementos a mostrar en la lista jerárquica
         AddDisplayedTreeItem(i);
        //--- Aumentamos el contador de los índices locales de los elementos
         ii++;
        //--- Si hemos llegado al último elemento en la carpeta raíz
         if(nl==m_min_node_level && ii>=m_root_items_total)
           {
            //--- Colocamos la bandera y terminamos el ciclo actual
            end_list=true;
            break;
           }
        //--- Si todavía no hemos llegado al último elemento en la carpeta raíz
         else if(nl>m_min_node_level)
           {
            //--- Obtenemos el número de elementos en el nodo actual
            int total=(m_file_navigator_mode==FN_ONLY_FOLDERS)? l_folders_total[nl]: l_items_total[nl];
            //--- Si no es último índice local del elemento, vamos al siguiente
            if(ii<total)
               continue;
            //--- Si hemos llegado al último índice local, 
            //    hay que volver al nodo anterior y continuar desde el elemento en el que hemos parado
            while(true)
              {
               //--- Reseteamos los valores del nodo actual en los arrays enumerados a continuación
               l_prev_node_list_index[nl] =-1;
               l_item_index[nl]           =-1;
               l_items_total[nl]          =-1;
               //--- Disminuimos el contador de nodos mientras que se cumpla la igualdad en el número de los elementos en las listas locales  
               //    o hasta que no lleguemos a la carpeta raíz
               if(l_item_index[nl-1]+1>=l_items_total[nl-1])
                 {
                  if(nl-1==m_min_node_level)
                     break;
                  //---
                  nl--;
                  continue;
                 }
               //---
               break;
              }
             //--- Vamos al nodo anterior
            nl=nl-2;
            //--- Ponemos a cero el contador de los índices locales de los elementos y vamos al siguiente nodo
            ii=0;
            break;
           }
        }
     }
//--- Redibujar el control
   RedrawTreeList();
  }

El desplazamiento de las listas respecto a los deslizadores de la barra de desplazamiento va a realizarse a través de los métodos CTreeView::ShiftTreeList() y CTreeView::ShiftContentList(). La lógica principal de estos métodos es prácticamente idéntica, con lo cual, como ejemplo, vamos a mostrar aquí el código sólo de uno de estos métodos (para la lista jerárquica). 

Al principio del método, todos los elementos de la lista jerárquica se ocultan. Luego, se calcula el ancho de los elementos tomando en cuenta la presencia de la barra de desplazamiento en la versión actual de la lista. Si hay barra de desplazamiento, se determina la posición del deslizador. Luego, para cada elemento se calculan y se actualizan en el ciclo las coordenadas y el ancho, y el elemento se hace visible. Al final del método, si la barra de desplazamiento debe mostrarse, hay que redibujarla para que se coloque sobre la lista. 

class CTreeView : public CElement
  {
private:
  //--- Desplazamiento de las listas
   void              ShiftTreeList(void);
   void              ShiftContentList(void);
  };
//+------------------------------------------------------------------+
//| Mueve la lista jerárquica respecto a la barra de desplazamiento        |
//+------------------------------------------------------------------+
void CTreeView::ShiftTreeList(void)
  {
//--- Ocultar todos los elementos en la lista jerárquica
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
      m_items[i].Hide();
//--- Si hace falta la barra de desplazamiento
   bool is_scroll=m_items_total>m_visible_items_total;
//--- Cálculo del ancho de los elementos de la lista
   int w=(is_scroll)? m_area.XSize()-m_scrollv.ScrollWidth()-1 : m_area.XSize()-2;
//--- Determinar la posición del scroll
   int v=(is_scroll)? m_scrollv.CurrentPos() : 0;
   m_scrollv.CurrentPos(v);
//--- Coordenada Y del primer elemento de la lista jerárquica
   int y=CElement::Y()+1;
//---
   for(int r=0; r<m_visible_items_total; r++)
     {
      //--- Comprobación para la prevención de superar el rango
      if(v>=0 && v<m_items_total)
        {
        //--- Calculamos la coordenada Y
         y=(r>0)? y+m_item_y_size-1 : y;
         //--- Obtenemos el índice general del elemento de la lista jerárquica
         int li=m_td_list_index[v];
        //--- Establecer las coordenadas y el ancho
         m_items[li].UpdateX(m_area.X()+1);
         m_items[li].UpdateY(y);
         m_items[li].UpdateWidth(w);
         //--- Mostrar el elemento
         m_items[li].Show();
         v++;
        }
     }
//--- Redibujar las barra de desplazamiento
   if(is_scroll)
      m_scrollv.Reset();
  }

El método para formar y actualizar la lista del contenido es considerablemente más sencillo, puesto que aquí se forma una lista común donde no es necesario cumplir la secuencia jerárquica. Al principio del método CTreeView::UpdateContentList(), se liberan los arrays que se usan para formar la lista del contenido. Luego, en el primer ciclo repasamos todos los elementos de la lista jerárquica y guardamos sólo aquellos cuyos parámetros siguientes coinciden con el elemento seleccionado en la lista jerárquica:

  • Niveles de los nodos
  • Índices locales de los elementos
  • Índices generales de los elementos.

En este ciclo se guarda sólo la descripción del elemento (texto a mostrar) y el índice general de la lista jerárquica

En el segundo ciclo, hay que repasar la lista del contenido y llenar el array para los índices generales de esta lista. Para determinar los elementos necesarios, van a utilizarse los parámetros obtenidos en el primer ciclo. Al final del método, se corrige el tamaño del deslizador de la barra de desplazamiento y se actualiza la lista para mostrar los últimos cambios. 

class CTreeView : public CElement
  {
private:
   //--- Actualiza la lista del contenido
   void              UpdateContentList(void);
  };
//+------------------------------------------------------------------+
//| Actualiza la lista del contenido                                      |
//+------------------------------------------------------------------+
void CTreeView::UpdateContentList(void)
  {
//--- Índice del elemento seleccionado
   int li=m_selected_item_index;
//--- Liberamos los arrays de la lista del contenido
   ::ArrayFree(m_cd_item_text);
   ::ArrayFree(m_cd_list_index);
   ::ArrayFree(m_cd_tree_list_index);
//--- Formamos la lista del contenido
   int items_total=::ArraySize(m_items);
   for(int i=0; i<items_total; i++)
     {
      //--- Si (1) los niveles de los nodos y (2) los índices locales de los elementos coinciden, así como
      //     coincide (3) el índice del nodo anterior con el índice del elemento seleccionado
      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)
        {
         //--- Aumentamos los arrays de los elementos a mostrar de la lista del contenido
         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);
         //--- Guardamos el texto del elemento y el índice general de la lista jerárquica en los arrays
         m_cd_item_text[size]       =m_t_item_text[i];
         m_cd_tree_list_index[size] =m_t_list_index[i];
        }
     }
//--- Si la lista no está vacía, llenamos el array de los índices generales de la lista del contenido
   int cd_items_total=::ArraySize(m_cd_list_index);
   if(cd_items_total>0)
     {
      //--- Contador de elementos
      int c=0;
      //--- Recorremos la lista
      int c_items_total=::ArraySize(m_c_list_index);
      for(int i=0; i<c_items_total; i++)
        {
         //--- Si la descripción y los índices generales de los elementos de la lista jerárquica coinciden
         if(m_c_item_text[i]==m_cd_item_text[c] && 
            m_c_tree_list_index[i]==m_cd_tree_list_index[c])
           {
            //--- Guardamos el índice general de la lista del contenido y vamos al siguiente
            m_cd_list_index[c]=m_c_list_index[i];
            c++;
            //--- Salir del ciclo si hemos llegado al final de la lista a mostrar
            if(c>=cd_items_total)
               break;
           }
        }
     }
//--- Corregir el tamaño del deslizador de la barra
   m_content_scrollv.ChangeThumbSize(cd_items_total,m_visible_items_total);
//--- Corregir la lista del contenido del elemento
   ShiftContentList();
  }

 


Gestión del ancho de las áreas de las listas

Ahora hablaremos con más detalles sobre el modo del cambio del ancho de las listas. Para eso vamos a necesitar: (1) un método privado principal CTreeView::ResizeListArea(), donde van a realizarse las comprobaciones principales y el cambio del ancho de las listas según los resultados de estas comprobaciones, así como (2) cuatro métodos privados auxiliares para solucionar las tareas descritas a continuación.

  • Método CTreeView::CheckXResizePointer() — comprobación de la preparación para el cambio del ancho de las listas. Aquí el código del método se compone de dos bloques vinculados a una condición. Si el puntero del cursor no está activado, pero el cursor del ratón se encuentra en su área, las coordenadas del puntero se actualizan y él se hace visible. Además, si el botón izquierdo se mantiene pulsado, el puntero se activa. Si la primera condición del método no se cumple, el puntero del cursor se desactiva y se oculta.
class CTreeView : public CElement
  {
private:
   //--- Comprobación de la preparación para el cambio del ancho de las listas
   void              CheckXResizePointer(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Comprobación de la preparación para el cambio del ancho de las listas                 |
//+------------------------------------------------------------------+
void CTreeView::CheckXResizePointer(const int x,const int y)
  {
//--- Si el puntero del cursor no está activado, pero el cursor del ratón se encuentra en su área
   if(!m_x_resize.State() && 
      y>m_area.Y() && y<m_area.Y2() && x>m_area.X2()-2 && x<m_area.X2()+3)
     {
      //--- Actualizar las coordenadas del puntero y hacerlo visible
      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();
      //--- Establecer la bandera de visibilidad
      m_x_resize.IsVisible(true);
      //--- Si el botón izquierdo del ratón está pulsado
      if(m_mouse_state)
         //--- Activamos el puntero
         m_x_resize.State(true);
     }
   else
     {
      //--- Si el botón izquierdo del ratón está suelto
      if(!m_mouse_state)
        {
         //--- Desactivamos y ocultamos el puntero
         m_x_resize.State(false);
         m_x_resize.Hide();
         //--- Quitar la bandera de visibilidad
         m_x_resize.IsVisible(false);
        }
     }
  }
  • El método CTreeView::CheckOutOfArea() — comprobar la superación de la limitación. No tiene sentido cambiar el ancho de la lista de tal manera que una de ellas se quede invisible. Por eso ponemos la limitación en 80 píxeles. Hagamos que si el cursor sale fuera de la limitación establecida por la horizontal, el desplazamiento del puntero será posible sólo por la vertical pero dentro de la zona donde lindan las áreas de las listas. 
class CTreeView : public CElement
  {
private:
   //--- Comprobar la superación de la limitación
   bool              CheckOutOfArea(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Comprobar la superación de la limitación                                 |
//+------------------------------------------------------------------+
bool CTreeView::CheckOutOfArea(const int x,const int y)
  {
//--- Limitación
   int area_limit=80;
//--- Salimos fuera de los límites del control por la horizontal ...
   if(x<m_area.X()+area_limit || x>m_content_area.X2()-area_limit)
     {
      // ... desplazamos el puntero sólo por la vertical, sin salir de los límites
      if(y>m_area.Y() && y<m_area.Y2())
         m_x_resize.UpdateY(y-m_x_resize.YGap());
      //--- No cambiar el ancho de las listas
      return(false);
     }
//--- Cambiar el ancho de las listas
   return(true);
  }
  • Los métodos CTreeView::UpdateTreeListWidth() y CTreeView::UpdateContentListWidth() se utilizan para la actualización (1) del ancho de la lista jerárquica y (2) el ancho de la lista en el área del contenido. En la lista jerárquica va a desplazarse sólo su borde derecho. Para eso hay que cambiar sólo su ancho. Aparte de eso, es necesario actualizar las coordenadas de la barra de desplazamiento. Para la lista en el área del contenido, hay que actualizar simultáneamente su ancho y la coordenada X, para que se desplazca visualmente sólo su borde izquierdo. 
class CTreeView : public CElement
  {
private:
   //--- Actualización del ancho de la lista jerárquica
   void              UpdateTreeListWidth(const int x);
   //--- Actualización del ancho de la lista del área del contenido
   void              UpdateContentListWidth(const int x);
  };
//+------------------------------------------------------------------+
//| Actualización del ancho de la lista jerárquica                            |
//+------------------------------------------------------------------+
void CTreeView::UpdateTreeListWidth(const int x)
  {
//--- Calculamos y establecemos el ancho del área de la lista jerárquica
   m_area.X_Size(x-m_area.X());
   m_area.XSize(m_area.X_Size());
//--- Calculamos y establecemos el ancho para los elementos en la lista jerárquica tomando en cuenta las barras de desplazamiento
   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);
//--- Calculamos y establecemos las coordenadas para la barra de desplazamiento de la lista jerárquica
   m_scrollv.X(m_area.X2()-m_scrollv.ScrollWidth());
   m_scrollv.XDistance(m_scrollv.X());
  }
//+------------------------------------------------------------------+
//| Actualización del ancho de la lista del área del contenido                    |
//+------------------------------------------------------------------+
void CTreeView::UpdateContentListWidth(const int x)
  {
//--- Calculamos y establecemos la coordenada X, el margen y el ancho para el área del contenido
   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());
//--- Calculamos y establecemos la coordenada X y el ancho para los elementos en el área del contenido
   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 estos métodos auxiliares van a invocarse en el método principal CTreeView::ResizeListArea(), que al final va a utilizarse durante el procesamiento de eventos de la aplicación. Al principio del método, hay que realizar algunas comprobaciones. El programa saldrá de aquí en las siguientes situaciones:

  • Si el modo para el cambio del ancho de las listas está desactivado
  • Si el área del contenido está desactivada
  • Si el modo de los elementos-pestañas está activado
  • Si la barra de desplazamiento está activa (el deslizador se encuentra en el modo de desplazamiento)

Si todas las comprobaciones han sido superadas con éxito, se invoca el método CTreeView::CheckXResizePointer() para determinar la preparación del estado para el cambio del ancho de las listas. Si resulta que el puntero está desactivado, hay que desbloquear el formulario bloqueado anteriormente. 

Si el puntero está activado, primero hay que comprobar si no salimos fuera de las limitaciones establecidas. En caso de superar las limitaciones, el programa termina su trabajo en este método. Si nos encontramos en el área de trabajo, el formulario se bloquea. Es obligatorio guardar el identificador del control, porque sólo el control que ha bloqueado el formulario debe ser capaz de desbloquearlo. Luego, se actualizan las coordenadas del puntero del cursor y las coordenadas de las listas y su ancho

//+------------------------------------------------------------------+
//| Controla el ancho de las listas                                        |
//+------------------------------------------------------------------+
void CTreeView::ResizeListArea(const int x,const int y)
  {
//--- Salir (1) si no es necesario cambiar el ancho del área del contenido o
//    (2) si el área del contenido está desactivada, o (3) o el modo de elementos-pestañas está activado
   if(!m_resize_list_area_mode || m_content_area_width<0 || m_tab_items_mode)
      return;
//--- Salir si la barra de desplazamiento está activa
   if(m_scrollv.ScrollState())
      return;
//--- Comprobación de la preparación para el cambio del ancho de las listas
   CheckXResizePointer(x,y);
//--- Si el puntero está desactivado, desbloquear el formulario
   if(!m_x_resize.State())
     {
      //--- El formulario puede desbloquearse sólo por el control que lo ha bloqueado
      if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()==CElement::Id())
        {
         m_wnd.IsLocked(false);
         m_wnd.IdActivatedElement(WRONG_VALUE);
         return;
        }
     }
   else
     {
      //--- Comprobación del exceso de las limitaciones establecidas 
      if(!CheckOutOfArea(x,y))
         return;
      //--- Bloqueamos el formulario y guardamos el identificador del elemento activo
      m_wnd.IsLocked(true);
      m_wnd.IdActivatedElement(CElement::Id());
      //--- Establecemos la coordenada X para el objeto por el centro del cursor del ratón
      m_x_resize.UpdateX(x-m_x_resize.XGap());
      //--- establecemos la coordenada Y sólo si no hemos salido fuera del área del control
      if(y>m_area.Y() && y<m_area.Y2())
         m_x_resize.UpdateY(y-m_x_resize.YGap());
      //--- Actualización del ancho de la lista jerárquica
      UpdateTreeListWidth(x);
       //--- Actualización del ancho de la lista del contenido
      UpdateContentListWidth(x);
      //--- Actualizar las coordenadas y el tamaño de las listas
      ShiftTreeList();
      ShiftContentList();
       //--- Redibujar el puntero
      m_x_resize.Reset();
     }
  }

Como un resumen intermedio se puede mostrar el bloque del código del manejador del control CTreeView::OnEvent(), en el que se procesa el evento del desplazamiento del ratón CHARTEVENT_MOUSE_MOVE. Muchos métodos considerados anteriormente se utilizan precisamente aquí. 

//+------------------------------------------------------------------+
//| Manejador de eventos                                               |
//+------------------------------------------------------------------+
void CTreeView::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Procesamiento del evento del desplazamiento del cursor  
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Salir si el control está ocultado
      if(!CElement::IsVisible())
         return;
       //--- Coordenadas y el estado del botón izquierdo del ratón
      int x=(int)lparam;
      int y=(int)dparam;
      m_mouse_state=(bool)int(sparam);
      //--- Comprobación del foco sobre la lista
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      //--- Desplazamos la lista jerárquica si el manejo del deslizador de la barra de desplazamiento está activado
      if(m_scrollv.ScrollBarControl(x,y,m_mouse_state))
        {
         ShiftTreeList();
         return;
        }
      //--- Desplazamos la lista del contenido si el manejo del deslizador está activado
      if(m_content_scrollv.ScrollBarControl(x,y,m_mouse_state))
        {
         ShiftContentList();
         return;
        }
      //--- Gestión del ancho del área del contenido
      ResizeListArea(x,y);
      //--- Salir si el formulario está bloqueado
      if(m_wnd.IsLocked())
         return;
       //--- Cambio del color al situar el cursor encima
      ChangeObjectsColor();
      return;
     }
  }

 

 

Modo de los elementos-pestañas

Ahora veremos cómo va a funcionar el modo de las pestañas en la lista jerárquica. En este momento la librería ya contiene dos clases para la creación de las pestañas: CTabs и CIconTabs. Para más detalles consulte el artículo Interfaces gráficas VII: Control “Pestañas” (Capítulo 2). En estas clases las pestañas se crean en línea por la horizontal o por la vertical. En la lista jerárquica se puede organizar los controles por categorías lo que supone una comodidad considerable cuando hay muchos controles en la interfaz gráfica de la aplicación y es necesario agruparlos de alguna manera.

Pues bien. Si durante la creación de una lista jerárquica ha sido establecido el modo de los elementos-pestañas, inmediatamente de la creación de todos los objetos del control hay que determinar los elementos de la lista jerárquica que van a ser pestañas. Las pestañas pueden ser sólo los elementos que no contienen las listas locales. En este caso hay que tomar en consideración que los elementos-pestañas van a tener su orden de indexación (véase la imagen de abajo). Precisamente a esta orden de índices hay que orientarse cuando añadimos los controles a una u otra pestaña.

En la imagen de abajo se muestra el ejemplo del orden de indexación de los elementos-pestañas. Los elementos A, D, G y H no son pestañas porque contienen las listas.

 

Fig. 17 Índices de los elementos-pestañas.

Para la solución de esta tarea vamos a necesitar una estructura (con la declaración del array de sus instancias) que va a contener el array dinámico para el almacenamiento de los punteros a los controles y el campo donde va a guardarse el índice de la pestaña:

class CTreeView : public CElement
  {
private:
   //--- Estructura de controles asignados a cada elemento--pestaña
   struct TVElements
     {
      CElement         *elements[];
      int               list_index;
     };
   TVElements        m_tab_items[];
  };

Para determinar los elementos que van a figurar como pestañas, así como para la formación de su array vamos a usar el método CTreeView::GenerateTabItemsArray(). Si el modo de los elementos-pestañas está desactivado, el programa saldrá en seguida del método. Luego, recorremos en el ciclo la lista jerárquica, y cada vez que encontremos un elemento vacío, aumentamos el array de la estructura TVElements a un control y guardamos el índice general del control

Luego, si la visualización del contenido de los elementos está activada, por defecto, queda seleccionado el primer elemento de la lista. Si la visualización del contenido de los elementos está desactivada, el índice se corrige en caso de salir fuera del rango. Luego, se selecciona la pestaña indicada en las propiedades. 

class CTreeView : public CElement
  {
private:
   //--- Forma el array de los elementos-pestañas
   void              GenerateTabItemsArray(void);
  };
//+------------------------------------------------------------------+
//| Forma el array de los elementos-pestañas                                 |
//+------------------------------------------------------------------+
void CTreeView::GenerateTabItemsArray(void)
  {
//--- Salir si el modo de los elementos-pestañas está desactivado
   if(!m_tab_items_mode)
      return;
//--- Añadimos sólo los elementos vacíos al array de los elementos-pestañas
   int items_total=::ArraySize(m_items);
   for(int i=0; i<items_total; i++)
     {
      //--- Ir al siguiente si este elemento contiene otros elementos
      if(m_t_items_total[i]>0)
         continue;
      //--- Aumentamos el tamaño del array de los elementos-pestañas a un elemento
      int array_size=::ArraySize(m_tab_items);
      ::ArrayResize(m_tab_items,array_size+1);
      //--- Guardamos el índice general del elemento
      m_tab_items[array_size].list_index=i;
     }
//--- Si el modo de visualización del contenido está desactivado
   if(!m_show_item_content)
     {
      //--- Obtenemos el tamaño del array de los elementos-pestañas
      int tab_items_total=::ArraySize(m_tab_items);
      //--- Corregimos el índice si salimos fuera del rango
      if(m_selected_item_index>=tab_items_total)
         m_selected_item_index=tab_items_total-1;
      //--- Desactivamos la selección del elemento actual en la lista
      m_items[m_selected_item_index].HighlightItemState(false);
       //--- Índice de la pestaña seleccionada
      int tab_index=m_tab_items[m_selected_item_index].list_index;
      m_selected_item_index=tab_index;
      //--- Seleccionamos este elemento
      m_items[tab_index].HighlightItemState(true);
     }
  }

Para adjuntar algún control a la pestaña en la lista jerárquica, hay que usar el método CTreeView::AddToElementsArray(). Este método tiene sólo dos argumentos: (1) índice del elemento-pestaña y (2) el objeto tipo CElement, cuyo puntero es necesario guardar en el array del elemento-pestaña especificado. 

class CTreeView : public CElement
  {
public:
   //--- Añade el control al array del elemento-pestaña
   void              AddToElementsArray(const int item_index,CElement &object);
  };
//+------------------------------------------------------------------+
//| Añade el control al array de la pestaña especificada                     |
//+------------------------------------------------------------------+
void CTreeView::AddToElementsArray(const int tab_index,CElement &object)
  {
//--- Comprobar la superación del rango
   int array_size=::ArraySize(m_tab_items);
   if(array_size<1 || tab_index<0 || tab_index>=array_size)
      return;
//--- Añadimos el puntero del control pasado al array de la pestaña 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);
  }

Para mostrar los controles sólo del elemento-pestaña seleccionado, se utiliza el método CTreeView::ShowTabElements(). Si resulta que el control está ocultado o el modo está desactivado, el programa saldrá del método. Luego, en el primer ciclo del método se determina el índice del elemento-pestaña seleccionado. Luego, en el segundo ciclo se muestran sólo los controles adjuntos a la pestaña seleccionada, todos los demás se ocultan. 

class CTreeView : public CElement
  {
public:
   //--- Mostrar los controles sólo de la pestaña seleccionada
   void              ShowTabElements(void);
  };
//+------------------------------------------------------------------+
//| Mostrar los controles sólo de la pestaña seleccionada            |
//+------------------------------------------------------------------+
void CTreeView::ShowTabElements(void)
  {
//--- Salir si el control está ocultado o el modo de los elementos-pestañas está desactivado
   if(!CElement::IsVisible() || !m_tab_items_mode)
      return;
//--- Índice de la pestaña seleccionada
   int tab_index=WRONG_VALUE;
//--- Determinamos el índice de la pestaña seleccionada
   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;
        }
     }
//--- Mostramos los controles sólo de la pestaña seleccionada
   for(int i=0; i<tab_items_total; i++)
     {
      //--- Obtenemos el número de controles adjuntados a la pestaña
      int tab_elements_total=::ArraySize(m_tab_items[i].elements);
      //--- Si este elemento-pestaña está seleccionado
      if(i==tab_index)
        {
         //--- Mostrar controles
         for(int j=0; j<tab_elements_total; j++)
            m_tab_items[i].elements[j].Reset();
        }
      else
        {
         //--- Ocultar controles
         for(int j=0; j<tab_elements_total; j++)
            m_tab_items[i].elements[j].Hide();
        }
     }
  }

 

 

Métodos para procesar los eventos

Al seleccionar un elemento en las listas, va a generarse el mensaje que avisa que la ruta hacia el elemento de la lista jerárquica ha sido cambiado. Se puede utilizar luego este evento en los exploradores de archivos para determinar la ruta hacia el archivo. Para la implementación de lo planeado, vamos a necesitar el identificador ON_CHANGE_TREE_PATH en el archivo Defines.mqh (véase el código de abajo):

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#define ON_CHANGE_TREE_PATH       (23) // La ruta en la lista jerárquica se ha cambiado

Para determinar la ruta hacia el elemento seleccionado, escribiremos el método público CTreeView::CurrentFullPath() al que vamos a llamar en el explorador de archivos para obtener la ruta hacia el archivo. Además, necesitaremos el campo y el método para obtener el archivo seleccionado en las listas del control. Serán útiles sólo cuando la lista jerárquica se utiliza como parte integrante del explorador de archivos. Analizaremos más detalladamente el método CTreeView::CurrentFullPath().

La ruta se forma en el array mediante la adición consecutiva de los elementos, empezando desde el elemento seleccionado en este momento, y luego hacia arriba siguiendo la jerarquía de la lista. Al principio, comprobamos si el elemento seleccionado es un archivo. Si es una carpeta, lo añadimos al array (los nombres de los archivos no se añaden a la ruta). 

Luego, repasamos en el ciclo la lista jerárquica completa, desde el elemento seleccionado hacia arriba. Eso está implementado de la siguiente manera. Omitimos todos los elementos que son archivos. Luego, para añadir el elemento al array, es necesario que tres condiciones sean verdaderas.

  • El índice de la lista general debe coincidir con el índice de la lista general del nodo anterior.
  • El índice del elemento de la lista local debe coincidir con el índice del elemento del nodo anterior.
  • Debe cumplirse la secuencia de los nodos a la disminución.

Si las tres condiciones se han cumplido: (1) el nombre del elemento se añade al array, (2) se guarda el índice actual del ciclo para la siguiente comprobación y (3) el contador del ciclo se pone a cero. Pero en caso, si llegamos al nivel cero de los nodos, el ciclo se detiene

Luego, en el ciclo separado, se forma la línea (ruta completa hacia el elemento seleccionado) con el separador «\\». Luego, si el elemento seleccionado en la lista jerárquica es una carpeta, comprobamos si hay un elemento seleccionado en la lista del área del contenido y si se trata de un archivo.  Si el modo “Mostrar archivos en la lista jerárquica” está activado, cuando el archivo está seleccionado, su nombre se guarda tras la formación de la línea.

Al final del método, se devuelve la línea que contiene la ruta hacia el elemento seleccionado. Si en la carpeta actual también está seleccionado un archivo, se puede obtenerlo usando el método público CTreeView::SelectedItemFileName()

class CTreeView : public CElement
  {
private:
   //--- Texto del elemento seleccionado en la lista.
   //    Sólo para los archivos, en caso de usar la clase para la creación del explorador de archivos.
   //    Si en la lista está seleccionado otro objeto que no es archivo, en este campo debe haber la línea vacía "".
   string            m_selected_item_file_name;
   //---
public:
   //--- Devuelve el nombre del archivo
   string            SelectedItemFileName(void)                   const { return(m_selected_item_file_name);        }
   //--- Devuelve la ruta completa del elemento seleccionado
   string            CurrentFullPath(void);
  };
//+------------------------------------------------------------------+
//| Devuelve la ruta completa actual                                   |
//+------------------------------------------------------------------+
string CTreeView::CurrentFullPath(void)
  {
//--- Para la formación del directorio hacia el elemento seleccionado
   string path="";
//--- Índice del elemento seleccionado
   int li=m_selected_item_index;
//--- Array para la formación del directorio
   string path_parts[];
//--- Obtenemos la descripción (texto) del elemento seleccionado,
//    pero sólo si es una carpeta
   if(m_t_is_folder[li])
     {
      ::ArrayResize(path_parts,1);
      path_parts[0]=m_t_item_text[li];
     }
//--- Repasamos la lista completa
   int total=::ArraySize(m_t_list_index);
   for(int i=0; i<total; i++)
     {
      //--- Consideramos sólo las carpetas.
      //    Si es un archivo, vamos al siguiente elemento.
      if(!m_t_is_folder[i])
         continue;
      //--- Si el índice de la lista general coincide con el índice de la lista general del nodo anterior y
      //    el índice del elemento de la lista local coincide con el índice del elemento del nodo anterior y
      //    se cumple la secuencia de los niveles de los nodos
      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)
        {
        //--- Aumentamos el array a un elemento y guardamos la descripción del elemento
         int sz=::ArraySize(path_parts);
         ::ArrayResize(path_parts,sz+1);
         path_parts[sz]=m_t_item_text[i];
         //--- Guardamos el índice para la siguiente comprobación
         li=i;
         //--- Si hemos llegado al nivel cero del nodo, salimos del ciclo
         if(m_t_node_level[i]==0 || i<=0)
            break;
         //--- Poner a cero el contador del ciclo
         i=-1;
        }
     }
//--- Formar la línea: ruta completa hacia el elemento seleccionado en la lista jerárquica
   total=::ArraySize(path_parts);
   for(int i=total-1; i>=0; i--)
      ::StringAdd(path,path_parts[i]+"\\");
//--- Si el elemento seleccionado en la lista jerárquica es una carpeta
   if(m_t_is_folder[m_selected_item_index])
     {
      m_selected_item_file_name="";
      //--- Si el elemento en el área del contenido está seleccionado
      if(m_selected_content_item_index>0)
        {
         //--- Si el elemento seleccionado es un archivo, guardamos su nombre
         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];
        }
     }
//--- Si el elemento seleccionado en la lista jerárquica es un archivo
   else
//--- Guardamos su nombre
      m_selected_item_file_name=m_t_item_text[m_selected_item_index];
//--- Devolver el directorio
   return(path);
  }

En la versión actual del control “Lista jerárquica” van a procesarse tres acciones del usuario.

  • Clic en el botón para expandir/reducir la lista local del elemento: método CTreeView::OnClickItemArrow(). Aquí, si resulta que el nombre del objeto gráfico no es de la lista jerárquica, o el identificador del control en el nombre del objeto no coincide con el identificador de la lista jerárquica, el programa sale del método. Luego, (1) hay que obtener el estado del botón del elemento y cambiarlo por el contrario, (2) actualizar la lista jerárquica mostrando los últimos cambios, (3) calcular la posición del deslizador de la barra de desplazamiento y (4) si el modo correspondiente está activado, mostrar los controles que pertenecen sólo al elemento-pestaña seleccionado en este momento. 
class CTreeView : public CElement
  {
private:
   //--- Procesamiento del clic en el botón para plegar/desplegar la lista del elemento
   bool              OnClickItemArrow(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Clic en el botón para plegar/desplegar la lista del elemento      |
//+------------------------------------------------------------------+
bool CTreeView::OnClickItemArrow(const string clicked_object)
  {
//--- Salimos si el nombre del objeto no coincide
   if(::StringFind(clicked_object,CElement::ProgramName()+"_0_treeitem_arrow_",0)<0)
      return(false);
//--- Obtenemos el identificador desde el nombre del objeto
   int id=IdFromObjectName(clicked_object);
//--- Salir si los identificadores no coinciden
   if(id!=CElement::Id())
      return(false);
//--- Obtenemos el índice del elemento en la lista general
   int list_index=IndexFromObjectName(clicked_object);
//--- Obtenemos el estado de la flecha del elemento y establecemos el estado contrario
   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]);
//--- Actualizar la lista jerárquica
   UpdateTreeViewList();
//--- Calcular la posición  del deslizador de la barra de desplazamiento
   m_scrollv.CalculateThumbY();
//--- Mostrar los controles de la pestaña seleccionada
   ShowTabElements();
   return(true);
  }
  • Clic en el elemento en la lista jerárquica – método CTreeView::OnClickItem(). Aquí, al principio también hay que realizar unas comprobaciones parecidas a las que están en la descripción anterior. Aparte de los demás, la salida del método también se realiza cuando una de las barras de desplazamiento del control está activa en el momento actual. 

Luego, repasamos la parte visible de la lista jerárquica en el ciclo. Encontramos el elemento pulsado. Si resulta que este elemento ya está seleccionado, entonces el programa sale del método. Si no está seleccionado, entonces en caso cuando el modo de elementos-pestañas está activado y la visualización del contenido del elemento seleccionado está desactivado, pero el elemento no incluye ninguna lista, el ciclo se detiene en seguida sin que ocurran cambios algunos. En caso contrario, el elemento pulsado se selecciona.

Después del ciclo, si hemos llegado hasta este momento, un nuevo elemento ha quedado seleccionado. Eso significa que es necesario resetear el índice del elemento seleccionado en la lista del área del contenido, así como su color. Por ejemplo, precisamente así está implementado en el explorador del sistema operativo Windows 7. Luego, la lista en el área del contenido se actualiza para mostrar los últimos cambios. Al final del método, se genera un mensaje con el identificador del evento ON_CHANGE_TREE_PATH, que puede ser procesado en el manejado de eventos de la aplicación de usuario o en algún otro control.  

class CTreeView : public CElement
  {
private:
   //--- Procesamiento del clic en un elemento de la lista jerárquica
   bool              OnClickItem(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Clic en el elemento en la lista jerárquica                           |
//+------------------------------------------------------------------+
bool CTreeView::OnClickItem(const string clicked_object)
  {
//--- Salimos si la barra de desplazamiento se encuentra en modo activo
   if(m_scrollv.ScrollState() || m_content_scrollv.ScrollState())
      return(false);
//--- Salimos si el nombre del objeto no coincide
   if(::StringFind(clicked_object,CElement::ProgramName()+"_0_treeitem_area_",0)<0)
      return(false);
//--- Obtenemos el identificador desde el nombre del objeto
   int id=IdFromObjectName(clicked_object);
//--- Salir si los identificadores no coinciden
   if(id!=CElement::Id())
      return(false);
//--- Obtenemos la posición actual del deslizador de la barra de desplazamiento
   int v=m_scrollv.CurrentPos();
//--- Recorremos la lista
   for(int r=0; r<m_visible_items_total; r++)
     {
      //--- Comprobación para la prevención de superar el rango
      if(v>=0 && v<m_items_total)
        {
         //--- Obtenemos el índice general del elemento
         int li=m_td_list_index[v];
         //--- Si ha sido seleccionado este elemento de la lista
         if(m_items[li].Object(0).Name()==clicked_object)
           {
            //--- Salimos si este elemento ya está seleccionado
            if(li==m_selected_item_index)
               return(false);
            //--- Si el modo de los elementos-pestañas está activado y el modo de visualización del contenido está desactivado,
            //    no vamos a seleccionar los elemento sin la lista
            if(m_tab_items_mode && !m_show_item_content)
              {
               //--- Si el elemento actual no incluye la lista, detenemos el ciclo
               if(m_t_items_total[li]>0)
                  break;
              }
            //--- Establecemos el color para el elemento anterior seleccionado
            m_items[m_selected_item_index].HighlightItemState(false);
            //--- Guardamos el índice para el elemento actual y cambiamos su color
            m_selected_item_index=li;
            m_items[li].HighlightItemState(true);
            break;
           }
         v++;
        }
     }
//--- Resetear los colores en el área del contenido
   if(m_selected_content_item_index>=0)
      m_content_items[m_selected_content_item_index].HighlightItemState(false);
//--- Resetear el elemento seleccionado
   m_selected_content_item_index=WRONG_VALUE;
//--- Actualizar la lista del contenido
   UpdateContentList();
//--- Calcular la posición  del deslizador de la barra de desplazamiento
   m_content_scrollv.CalculateThumbY();
//--- Corregir la lista del contenido
   ShiftContentList();
//--- Mostrar los controles de la pestaña seleccionada
   ShowTabElements();
//--- Enviar mensaje sobre la selección del nuevo directorio en la lista jerárquica
   ::EventChartCustom(m_chart_id,ON_CHANGE_TREE_PATH,0,0,"");
   return(true);
  }
  • Clic en el elemento en la lista del contenido – método CTreeView::OnClickContentListItem(). El código de este método parece al código en la descripción anterior. Incluso es más sencillo, por eso no vamos a mostrarlo aquí. Lo único que merece la pena mencionar es que al pinchar en el elemento en el área del contenido, también se genera el evento con el identificador ON_CHANGE_TREE_PATH, lo que por ejemplo en el explorador de archivos puede significar la selección de un archivo.

En total, durante la interacción con el control mediante los métodos analizados en este artículo, sus ambas listas (si el área del contenido está activada) van a rediseñarse “al vuelo”. A continuación, se puede analizar más detalladamente el bloque del código en el manejador de eventos del control con los métodos arriba mencionados:

//+------------------------------------------------------------------+
//| Manejador de eventos                                               |
//+------------------------------------------------------------------+
void CTreeView::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Procesamiento del evento del desplazamiento del cursor  
//...
//--- Procesamiento del evento del clic izquierdo en el objeto
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Salir si estamos en el modo del cambio del tamaño del área de la lista del contenido
      if(m_x_resize.IsVisible() || m_x_resize.State())
         return;
      //--- Procesamiento del clic en la flecha del elemento
      if(OnClickItemArrow(sparam))
         return;
       //--- Procesamiento del clic en un elemento de la lista jerárquica
      if(OnClickItem(sparam))
         return;
       //--- Procesamiento del clic en un elemento de la lista del contenido
      if(OnClickContentListItem(sparam))
         return;
      //--- Mueve la lista respecto a la barra de desplazamiento
      if(m_scrollv.OnClickScrollInc(sparam) || m_scrollv.OnClickScrollDec(sparam))
         ShiftTreeList();
      if(m_content_scrollv.OnClickScrollInc(sparam) || m_content_scrollv.OnClickScrollDec(sparam))
         ShiftContentList();
      //---
      return;
     }
  }

La primera versión de la clase para la creación del control “Lista jerárquica” ya está lista. Pero para un correcto funcionamiento en todos sus modos, ahora es necesario integrarlo en el motor de la librería. 



Integración del control en el motor de la librería

Los archivos con las clase de la lista jerárquica deben estar incluidos en el archivo WndContainer.mqh (véase el código de abajo). Vamos a añadir el array privado para la lista jerárquica a la estructura de los arrays del control. Aparte de eso, necesitaremos los métodos para obtener el número de las listas jerárquicas y para almacenar los punteros de los controles que forman parte de la lista jerárquica. Puede estudiar al detalle el código de estos métodos en los archivos adjuntos al artículo.

#include "TreeItem.mqh"
#include "TreeView.mqh"
//+------------------------------------------------------------------+
//| Clase para almacenar todos los objetos de la interfaz                      |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- Array de ventanas
   CWindow          *m_windows[];
   //--- Estructura de los arrays de controles
   struct WindowElements
     {
      //--- Listas jerárquicas
      CTreeView        *m_treeview_lists[];
     };
   //--- Array de los arrays de los controles para cada ventana
   WindowElements    m_wnd[];
   //---
public:
  //--- Número de listas jerárquicas
   int               TreeViewListsTotal(const int window_index);
   //---
private:
  //--- Guarda los punteros a los controles de las listas jerárquicas
   bool              AddTreeViewListsElements(const int window_index,CElement &object);
  };

Hay que introducir algunas adiciones a la clase derivada CWndEvents de la clase CWndContainer, donde se procesan los eventos principales de la librería. En primer lugar, al desplegar el formulario cuando la lista jerárquica se utiliza como pestañas para el grupo de controles en la interfaz gráfica, hay que mostrar los controles sólo de la pestaña que se encuentra seleccionada en este momento. Por eso, al método CWndEvents::OnWindowUnroll() es necesario añadir el código tal como se muestra a continuación. Precisamente para estas ocasiones, los controles se distribuyen por sus arrays personales. En vez de recorrer en el ciclo el array general de todos los controles, será suficiente repasar sólo los array personales de los controles necesarios en esta determinada situación. 

//+------------------------------------------------------------------+
//| Evento ON_WINDOW_UNROLL                                         |
//+------------------------------------------------------------------+
bool CWndEvents::OnWindowUnroll(void)
  {
//--- Si hay la señal “Maximizar el formulario”
   if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_UNROLL)
      return(false);
//--- Índice de la ventana activa
   int awi=m_active_window_index;
//--- Si el identificador de la ventana y el número de la subventana coinciden
   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++)
        {
         //--- Mostrar todos los controles excepto el formulario y
         if(m_wnd[awi].m_elements[e].ClassName()!="CWindow")
           {
          //   los controles que son desplegables
            if(!m_wnd[awi].m_elements[e].IsDropdown())
               m_wnd[awi].m_elements[e].Show();
           }
        }
       //--- Si hay pestañas, mostrar sólo los controles de la pestaña seleccionada
      int tabs_total=CWndContainer::TabsTotal(awi);
      for(int t=0; t<tabs_total; t++)
         m_wnd[awi].m_tabs[t].ShowTabElements();
       //--- Si hay listas jerárquicas, mostrar sólo los controles del elemento-pestaña seleccionado
      int treeview_total=CWndContainer::TreeViewListsTotal(awi);
      for(int tv=0; tv<treeview_total; tv++)
         m_wnd[awi].m_treeview_lists[tv].ShowTabElements();
     }
//--- Actualizar la posición de todos los controles
   MovingWindow();
   m_chart.Redraw();
   return(true);
  }

Hay que añadir el mismo ciclo al método CWndEvents::OnOpenDialogBox() inmediatamente después del ciclo similar para los controles tipo CTabs (pestañas). No vamos a mostrar aquí este código para ahorrar el espacio del artículo. 

Es importante no olvidar liberar el array personal para las listas jerárquicas. Es necesario insertar la siguiente línea al método CWndEvents::Destroy(), como ha sido hecho ahí para los demás arrays personales de los controles: 

::ArrayFree(m_wnd[w].m_treeview_lists);

 

 

Prueba del control “Lista jerárquica”

Todo está listo para probar el control “Lista jerárquica”. Para la prueba vamos a usar el Asesor Experto del artículo anterior. Eliminamos de ahí todos los controles, salvo el menú principal y la barra de estado. Luego, declaramos la instancia de la clase CTreeView, método para la creación de la lista jerárquica y los márgenes desde el punto extremos del formulario al que será a adjuntado el control:

class CProgram : public CWndEvents
  {
private:
   //--- Lista jerárquica
   CTreeView         m_treeview;
   //---
private:
   //--- Lista jerárquica
#define TREEVIEW1_GAP_X       (2)
#define TREEVIEW1_GAP_Y       (43)
   bool              CreateTreeView(void);
  };

Como ejemplo, crearemos la lista jerárquica que ha sido mostrada en la imagen 12 de este artículo (véase el código de abajo). En total, la lista tendrá 25 elementos. El número de elementos visibles serán 10. En la misma sección del artículo hemos mencionado que durante la creación de este tipo de la lista hay que establecer sus parámetros personalmente. Antes de empezar a formar los arrays con los parámetros para cada elemento, es mejor primero mostrar todo eso en algún editor de tablas. Esta simple visualización facilitará la tarea y reducirá la posibilidad de cometer algún error. 

Estableceremos las imágenes personalizadas para cada grupo de elementos. El primer elemento va a contener la lista. En nuestro ejemplo la hacemos desplegada durante la creación del elemento (estado true), y las listas de los demás elementos que las tienen, las dejaremos plegadas (estado false). Activamos los modos de (1) resaltar el elemento al situar el cursor encima, (2) mostrar el contenido del elemento en el área colindante y (3) cambiar el ancho de las áreas de las listas

Una vez establecidas todas las propiedades del control, usamos el método CTreeView::AddItem() para añadir en el ciclo los elementos con parámetros especificados en los arrays a la lista. Después de eso se crea la lista jerárquica, y luego el puntero a ella se guarda en la base de los controles. 

//+------------------------------------------------------------------+
//| Crea la lista jerárquica                                      |
//+------------------------------------------------------------------+
bool CProgram::CreateTreeView(void)
  {
//--- Número de elementos en la lista jerárquica
#define TREEVIEW_ITEMS 25
//--- Guardamos el puntero a la ventana
   m_treeview.WindowPointer(m_window1);
//--- Coordenadas
   int x=m_window1.X()+TREEVIEW1_GAP_X;
   int y=m_window1.Y()+TREEVIEW1_GAP_Y;
//--- Formamos los arrays  para la lista jerárquica:
//    Imágenes para los elementos
#define A "Images\\EasyAndFastGUI\\Icons\\bmp16\\advisor.bmp"   // Эксперт
#define I "Images\\EasyAndFastGUI\\Icons\\bmp16\\indicator.bmp" // Индикатор
#define S "Images\\EasyAndFastGUI\\Icons\\bmp16\\script.bmp"    // Скрипт
   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};
//--- Descripción de los elementos (texto a mostrar)
   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"};
//--- Índices de la lista general de nodos 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 los elementos en listas locales
   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 del nivel del nodo
   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 locales de los elementos de nodos 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 los elementos en listas locales
   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 de la lista del elemento
   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};
//--- Establecemos las propiedades antes de la creación
   m_treeview.TreeViewAreaWidth(180);
   m_treeview.ContentAreaWidth(0);
   m_treeview.VisibleItemsTotal(10);
   m_treeview.LightsHover(true);
   m_treeview.ShowItemContent(true);
   m_treeview.ResizeListAreaMode(true);
//--- Propiedades de las barras de desplazamiento
   m_treeview.GetScrollVPointer().AreaBorderColor(clrLightGray);
   m_treeview.GetContentScrollVPointer().AreaBorderColor(clrLightGray);
//--- Añadimos los elementos
   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);
//--- Crear la lista jerárquica
   if(!m_treeview.CreateTreeView(m_chart_id,m_subwin,x,y))
      return(false);
//--- Añadimos el puntero al control a la base
   CWndContainer::AddToElementsArray(0,m_treeview);
   return(true);
  }

La llamada a este método debe realizarse en el método principal de la creación de la interfaz gráfica de la aplicación MQL. En este caso, este método se llama CProgram::CreateExpertPanel().

Por favor, compile el programa e inicie el EA en el gráfico. En la imagen de abajo se muestra el resultado que se debe obtener. Se ve que el primer elemento está expandido y seleccionado. Su contenido se muestra en el área a la derecha.

 Fig. 18. Prueba del control “Lista jerárquica”. Sólo la lista del primer elemento está desplegada.

Fig. 18 Prueba del control “Lista jerárquica”. Sólo la lista del primer elemento está desplegada.

Como demostración, vamos a desplegar las listas de todos los elementos. Para eso hay que pulsar el botón Flecha del elemento. Hagámoslo y seleccionemos el elemento con la descripción “Scripts” para mostrar su lista en el área del contenido. El resultado se muestra en la imagen de abajo. Se observa que cuando el número de elementos no cabe en el rango de 10 elementos visibles, aparecen las barras de desplazamiento. En el área del contenido está seleccionado el tercer elemento. También se ve que al situar el cursor sobre la zona donde lindan las áreas de las listas, aparece el puntero personalizado (flecha doble) del cursor del ratón.

Fig. 19. Las listas de todos los elementos están expandidas. 

Fig. 19 Las listas de todos los elementos están expandidas.

Vamos a crear otro EA de prueba, en el que demostraremos el modo de los elementos-pestañas. Como ejemplo, hagamos tres listas desplegables según el esquema descrita en la imagen de abajo:

 

Fig. 20 Esquema de la lista jerárquica.

Asignamos los controles como checkbox (CCheckBox) y las tablas tipo CTable a los elementos-pestañas de las listas “Advisors” y “Indicators”. Dejamos los elementos-pestañas de la lista “Scripts” en blanco, para que tenga la posibilidad de practicar. No vamos a mostrar el código entero. Sólo cabe mencionar qué modos y propiedades clave van a utilizarse para esta versión: (1) el modo de los elementos-pestañas está activado, (2) la visualización del contenido está desactivada y (3) seleccionamos el tercer elemento-pestaña

//...
   m_treeview.TabItemsMode(true);
   m_treeview.LightsHover(true);
   m_treeview.ShowItemContent(false);
   m_treeview.SelectedItemIndex((m_treeview.SelectedItemIndex()==WRONG_VALUE) ? 3 : m_treeview.SelectedItemIndex());
//--- Propiedades de las barras de desplazamiento
//...

Nos queda compilar el programa e iniciarlo en el gráfico. En la captura de pantalla de abajo se muestra el resultado final:

 Fig. 21. Prueba del modo “Elementos-pestañas”.

Fig. 21 Prueba del modo “Elementos-pestañas”. 

 


Conclusión

En este artículo (el segundo capítulo de la octava parte de la serie), hemos analizado uno de los control más complejos de la interfaz gráfica, “Lista jerárquica”. En total, hemos demostrado tres clases de los controles: 

  • Clase CPointer para la creación del puntero personalizado del cursor del ratón.
  • Clase CTreeItem para la creación del elemento de la lista jerárquica.
  • Clase CTreeView para la creación de la lista jerárquica.

En el siguiente artículo vamos a desarrollar este tema y crearemos una clase del código bastante útil que nos permitirá diseñar fácilmente un explorador de archivos en nuestra aplicación MQL. 

Más abajo puede descargar el material de la séptima parte de la serie para poder probar cómo funciona todo eso. Si le surgen algunas preguntas sobre el uso del material de estos archivos, puede dirigirse a la descripción detallada del proceso de desarrollo de la librería en uno de los artículos listados más abajo, o bien hacer su pregunta en los comentarios para el artículo.

Lista de artículos (capítulos) de la octava parte:


Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/2539

Archivos adjuntos |
Interfaces gráficas VIII: Control "Explorador de archivos" (Capítulo 3) Interfaces gráficas VIII: Control "Explorador de archivos" (Capítulo 3)
En los capítulos anteriores de la octava parte de la serie, nuestra librería se ha completado con las clases para la creación de los punteros para el cursor del ratón, calendarios y listas jerárquicas. En este artículo vamos a analizar el control “Explorador de archivos” que también puede utilizarse como parte de la interfaz gráfica de la aplicación MQL.
Qué comprobaciones debe superar un robot comercial antes de ser publicado en el Mercado Qué comprobaciones debe superar un robot comercial antes de ser publicado en el Mercado
Antes de su publicación, todos los productos del Mercado pasan por una comprobación preliminar de carácter obligatorio, con objeto de proporcionar un estándar único de calidad. En este artículo hablaremos de los errores más frecuentes que cometen los desarrolladores en sus indicadores técnicos y robots comerciales. Asimismo, mostraremos cómo puede usted comprobar por sí mismo su producto antes de enviarlo al Mercado.
Trabajando con sockets en MQL, o Cómo convertirse en proveedor de señales Trabajando con sockets en MQL, o Cómo convertirse en proveedor de señales
Los sockets... ¿Qué podría existir sin ellos en este mundo de información? Aparecieron por primera vez en 1982 y prácticamente no han cambiado hasta el día de hoy, siguen funcionando para nosotros cada segundo. Son la base de una red, las terminaciones nerviosas del Matrix en el que vivimos.
LifeHack para tráders: indicadores de balance, reducción, carga y ticks durante la simulación LifeHack para tráders: indicadores de balance, reducción, carga y ticks durante la simulación
¿Cómo convertir la simulación en algo más visual? La respuesta es sencilla: hay que usar en el simulador uno o varios indicadores, un indicador de ticks, un indicador de balance y equidad, un indicador de reducción y carga del depósito. Esto permitirá realizar un seguimiento visual de la naturaleza de los ticks, o de los cambios de balance y equidad, o de la reducción y la carga del depósito.