English Русский 中文 Deutsch 日本語 Português
Interfaces gráficas VIII: Control "Explorador de archivos" (Capítulo 3)

Interfaces gráficas VIII: Control "Explorador de archivos" (Capítulo 3)

MetaTrader 5Ejemplos | 29 julio 2016, 10:28
1 089 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 primero y el segundo capítulos de la octava parte de la serie, la librería se ha completado con las clases para la creación de los punteros para el cursor del ratón (CPointer), calendario (clases CCalendar y CDropCalendar) y listas jerárquicas (clases CTreeItem y CTreeView). En este artículo, vamos a desarrollar el tema del capítulo anterior y analizaremos el control “Explorador de archivos”. 

 


Control “Explorador de archivos”

El explorador de archivos (gestor o administrador de archivos) es una especie de guía que permite ver los elementos del sistema de archivos en la representación jerárquica a través de la interfaz gráfica del programa que se utiliza. Además, permite acceder a cada elemento de esta jerarquía y realizar alguna acción con él: abrir el archivo para ver los datos, guardar los datos en el archivo, mover el archivo, etc.

En este artículo veremos la primera versión del explorador de archivos. Ofrece las siguientes posibilidades al usuario:

  • desplazarse por el “entorno protegido” de archivos (file sandbox) del terminal sin abandonar la interfaz gráfica de la aplicación MQL;
  • encontrar las carpetas y archivos necesarios para el trabajo en la carpeta común de terminales y en la carpeta local del terminal de cliente;
  • guardar la ruta para el siguiente acceso hacia la carpeta seleccionada en el explorador de archivos o hacia un determinado archivo. 

Nota del Manual de referencia:

Por razones de seguridad el trabajo con los archivos en el lenguaje MQL5 se encuentra bajo un estricto control. Los archivos con los que se realizan las operaciones de archivos utilizando los medios del lenguaje MQL5 no pueden estar fuera del “entorno protegido” de archivos (file sandbox). El archivo se abre en la carpeta del terminal de cliente en la subcarpeta MQL5\Files (o carpeta_del_agente_de_prueba\MQL5\Files en caso de la prueba). Si entre las banderas, se especifica FILE_COMMON, el archivo se abre en la carpeta común de todos los terminales de cliente \Terminal\Common\Files.

 


Desarrollo de la clase CFileNavigator

Para crear el explorador de archivos, la librería ya posee todo lo necesario en la fase actual del desarrollo. El control “Lista jerárquica” presentado anteriormente, en realidad, es la base para la creación de los exploradores de archivos. Aparte de la lista jerárquica con el área del contenido, crearemos la barra de direcciones donde va a mostrarse la ruta completa respecto al elemento seleccionado en este momento en la lista jerárquica.

Vamos a dar la posibilidad de elegir los directorios a mostrar en la carpeta raíz. Por ejemplo, se puede limitarse con uno de los directorios del “entorno protegido” de archivos (file sandbox) del terminal, o se puede dar el acceso a los dos. Para eso insertamos la enumeración ENUM_FILE_NAVIGATOR_CONTENT en el archivo Enums.mqh (véase el código de abajo).

  • FN_BOTH – mostrar ambos directorios.
  • FN_ONLY_MQL – mostrar el directorio sólo de la carpeta local del terminal de cliente.
  • FN_ONLY_COMMON – mostrar el directorio sólo de la carpeta común de todos los terminales instalados.
//+------------------------------------------------------------------+
//| Enumeración del contenido del explorador de archivos                    |
//+------------------------------------------------------------------+
enum ENUM_FILE_NAVIGATOR_CONTENT
  {
   FN_BOTH        =0,
   FN_ONLY_MQL    =1,
   FN_ONLY_COMMON =2
  };

Luego, creamos el archivo FileNavigator.mqh con la clase CFileNavigator y lo incluimos en el motor de la librería (archivo WndContainer.mqh):

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

El conjunto estándar para todos los controles de la librería de los métodos debe estar implementado en la clase CFileNavigator, tal como se muestra a continuación: 

//+------------------------------------------------------------------+
//| Clase para la creación del explorador de archivos                          |
//+------------------------------------------------------------------+
class CFileNavigator : public CElement
  {
private:
   //--- Puntero al formulario al que está adjuntado el control
   CWindow          *m_wnd;
   //---
public:
                     CFileNavigator(void);
                    ~CFileNavigator(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);
   //--- Restablecer el color
   virtual void      ResetColors(void) {}
  };

Abajo se listan las propiedades que estarán disponibles para el usuario de la librería para configurar el explorador de archivos.

  • Ancho del área de la lista jerárquica
  • Ancho del área del contenido
  • Color del fondo de áreas
  • Color de los bordes
  • Color del fondo de la barra de direcciones
  • Colores del texto en la barra de direcciones
  • Alto de la barra de direcciones
  • Imágenes para las carpetas y archivos
  • Modo del explorador (Mostrar todo/sólo carpetas)
  • Modo del contenido del explorador de archivos (Carpeta compartida/Local/Todo)
//+------------------------------------------------------------------+
//| Clase para la creación del explorador de archivos                          |
//+------------------------------------------------------------------+
class CFileNavigator : public CElement
  {
private:
   //--- Ancho del área de la lista jerárquica
   int               m_treeview_area_width;
   //--- Ancho del área del contenido
   int               m_content_area_width;
   //--- Color del fondo y del marco del fondo
   color             m_area_color;
   color             m_area_border_color;
   //--- Color del fondo de la barra de direcciones
   color             m_address_bar_back_color;
   //--- Colores del texto en la barra de direcciones
   color             m_address_bar_text_color;
   //--- Alto de la barra de direcciones
   int               m_address_bar_y_size;
   //--- Imágenes para las (1) carpetas y (2) archivos
   string            m_file_icon;
   string            m_folder_icon;
   //--- Modo del contenido del explorador de archivos
   ENUM_FILE_NAVIGATOR_CONTENT m_navigator_content;
   //--- Prioridades para el clic izquierdo del ratón
   int               m_zorder;
   //---
public:
   //--- (1) Modo del explorador (Mostrar todo/sólo carpetas), (2) contenidos del navegador (carpeta compartida/local/todo)
   void              NavigatorMode(const ENUM_FILE_NAVIGATOR_MODE mode)       { m_treeview.NavigatorMode(mode);            }
   void              NavigatorContent(const ENUM_FILE_NAVIGATOR_CONTENT mode) { m_navigator_content=mode;                  }
   //--- (1) Alto de la barra de direcciones, (2) ancho de la lista jerárquica y (3) lista del contenido
   void              AddressBarYSize(const int y_size)                        { m_address_bar_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;               }
   //--- (1) Color del fondo y (2) del marco del fondo
   void              AreaBackColor(const color clr)                           { m_area_color=clr;                          }
   void              AreaBorderColor(const color clr)                         { m_area_border_color=clr;                   }
   //--- (1) Color del fondo y (2) del texto de la barra de direcciones
   void              AddressBarBackColor(const color clr)                     { m_address_bar_back_color=clr;              }
   void              AddressBarTextColor(const color clr)                     { m_address_bar_text_color=clr;              }
   //--- Establecer la ruta hacia los archivos para (1) los archivos y (2) carpetas
   void              FileIcon(const string file_path)                         { m_file_icon=file_path;                     }
   void              FolderIcon(const string file_path)                       { m_folder_icon=file_path;                   }
  };

Por defecto, la inicialización de los campos de propiedades con los valores va a realizarse en el constructor de la clase (véase el código de abajo). Las imágenes predefinidas para las carpetas y archivos del navegador estarán incluidas en la librería como recursos. Puede descargarlas del archivo adjunto al artículo.

#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\folder.bmp"
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\text_file.bmp"

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CFileNavigator::CFileNavigator(void) : m_address_bar_y_size(20),
                                       m_treeview_area_width(300),
                                       m_content_area_width(0),
                                       m_navigator_content(FN_ONLY_MQL),
                                       m_area_border_color(clrLightGray),
                                       m_address_bar_back_color(clrWhiteSmoke),
                                       m_address_bar_text_color(clrBlack),
                                       m_file_icon("Images\\EasyAndFastGUI\\Icons\\bmp16\\text_file.bmp"),
                                       m_folder_icon("Images\\EasyAndFastGUI\\Icons\\bmp16\\folder.bmp")
  {
//--- 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 creación del explorador de archivos, vamos a necesitar dos métodos privados y uno público. La estructura jerárquica del sistema de archivos será implementada a través de la clase CTreeView que ha sido presentada en el artículo anterior. Hay que incluir el archivo con esta clase en el archivo FileNavigator.mqh.

//+------------------------------------------------------------------+
//|                                                FileNavigator.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
#include "TreeView.mqh"
//+------------------------------------------------------------------+
//| Clase para la creación del explorador de archivos                          |
//+------------------------------------------------------------------+
class CFileNavigator : public CElement
  {
private:
   //--- Objetos para crear el control
   CRectCanvas       m_address_bar;
   CTreeView         m_treeview;
   //---
public:
   //--- Métodos para la creación del explorador de archivos
   bool              CreateFileNavigator(const long chart_id,const int subwin,const int x,const int y);
   //---
private:
   bool              CreateAddressBar(void);
   bool              CreateTreeView(void);
  };

Antes de describir algunos detalles de los métodos para la creación del explorador de archivos, vamos a ver cómo se forma la estructura jerárquica del sistema de archivos del terminal antes de la creación del control. 

 


Formación de la estructura jerárquica del sistema de archivos

Antes de crear el navegador de archivos, primero hay que escanear el sistema del terminal y guardar los parámetros de todos los controles para la creación de la lista jerárquica. Hemos analizado detalladamente todos estos parámetros en el artículo anterior, por eso aquí mostramos sólo la lista de los arrays para los parámetros que hay que guardar para la formación de la lista jerárquica.

  • Índice general
  • Índice general del nodo anterior
  • Nombre de la carpeta/archivo
  • Índice local
  • Nivel del nodo
  • Índice local del nodo anterior
  • Número total de elementos en la carpeta
  • Número de carpetas dentro de la carpeta
  • Indicio de la carpeta
  • Estado del elemento (expandido/reducido)

Para la preparación de los parámetros necesitaremos dos listas de archivos: principal (prefijo g) y auxiliar (prefijo l):

class CFileNavigator : public CElement
  {
private:
   //--- Arrays principales para almacenar los datos
   int               m_g_list_index[];           // índice general
   int               m_g_prev_node_list_index[]; // índice general del nodo anterior
   string            m_g_item_text[];            // nombre de la carpeta/archivo
   int               m_g_item_index[];           // índice local
   int               m_g_node_level[];           // nivel del nodo
   int               m_g_prev_node_item_index[]; // índice local del nodo anterior
   int               m_g_items_total[];          // total de elementos en la carpeta
   int               m_g_folders_total[];        // número de carpetas dentro de la carpeta
   bool              m_g_is_folder[];            // indicio de la carpeta
   bool              m_g_item_state[];           // estado del elemento (expandido/reducido)
   //--- Arrays auxiliares para recopilación de datos
   int               m_l_prev_node_list_index[];
   string            m_l_item_text[];
   string            m_l_path[];
   int               m_l_item_index[];
   int               m_l_item_total[];
   int               m_l_folders_total[];
  };

Para escanear el sistema de archivos del terminal, recopilar todos los datos y guardarlos en los arrays, hacen falta unos métodos auxiliares. Para trabajar con los arrays auxiliares, hace falta el método CFileNavigator::AuxiliaryArraysResize(), que permite cambiar su tamaño en función del nivel del nodo que se utiliza en este momento en el trabajo (véase el código de abajo). En otras palabras, el tamaño del array va a establecerse a un elemento más que el valor actual del nivel del nodo. Hay que pasar este valor al método como argumento único. Si el valor actual del nivel del nodo es 0, el tamaño de los arrays va a establecerse igual a 1, si el nivel del nodo es 1, el tamaño de los arrays será 2, etc. Puesto que en el proceso de recopilación de datos, el nivel del nodo va a variarse para más y para menos, de manera correspondiente va a regularse también el tamaño de los arrays. En este mismo método va a realizarse la inicialización del elemento del array del nodo actual. 

class CFileNavigator : public CElement
  {
private:
  //--- Aumenta el tamaño de los arrays a un elemento
   void              AuxiliaryArraysResize(const int node_level);
  };
//+------------------------------------------------------------------+
//| Aumenta el tamaño de los arrays auxiliares a un elemento      |
//+------------------------------------------------------------------+
void CFileNavigator::AuxiliaryArraysResize(const int node_level)
  {
   int new_size=node_level+1;
   ::ArrayResize(m_l_prev_node_list_index,new_size);
   ::ArrayResize(m_l_item_text,new_size);
   ::ArrayResize(m_l_path,new_size);
   ::ArrayResize(m_l_item_index,new_size);
   ::ArrayResize(m_l_item_total,new_size);
   ::ArrayResize(m_l_folders_total,new_size);
//--- Inicialización del último valor
   m_l_prev_node_list_index[node_level] =0;
   m_l_item_text[node_level]            ="";
   m_l_path[node_level]                 ="";
   m_l_item_index[node_level]           =-1;
   m_l_item_total[node_level]           =0;
   m_l_folders_total[node_level]        =0;
  }

Para añadir el elemento con los parámetros a los arrays principales, va a utilizarse el método CFileNavigator::AddItem(). Aquí, con cada llamada al método, los arrays van a aumentarse a un elemento, en la última celda del cual van a guardarse los valores de los argumentos pasados. 

class CFileNavigator : public CElement
  {
private:
   //--- Añade el elemento a los arrays
   void              AddItem(const int list_index,const string item_text,const int node_level,const int prev_node_item_index,
                             const int item_index,const int items_total,const int folders_total,const bool is_folder);
  };
//+------------------------------------------------------------------+
//| Añade el elemento con parámetros especificados a los arrays               |
//+------------------------------------------------------------------+
void CFileNavigator::AddItem(const int list_index,const string item_text,const int node_level,const int prev_node_item_index,
                             const int item_index,const int items_total,const int folders_total,const bool is_folder)
  {
//--- Aumentamos el tamaño del array a un elemento
   int array_size =::ArraySize(m_g_list_index);
   int new_size   =array_size+1;
   ::ArrayResize(m_g_prev_node_list_index,new_size);
   ::ArrayResize(m_g_list_index,new_size);
   ::ArrayResize(m_g_item_text,new_size);
   ::ArrayResize(m_g_item_index,new_size);
   ::ArrayResize(m_g_node_level,new_size);
   ::ArrayResize(m_g_prev_node_item_index,new_size);
   ::ArrayResize(m_g_items_total,new_size);
   ::ArrayResize(m_g_folders_total,new_size);
   ::ArrayResize(m_g_is_folder,new_size);
//--- Guardamos los valores de los parámetros pasados
   m_g_prev_node_list_index[array_size] =(node_level==0)? -1 : m_l_prev_node_list_index[node_level-1];
   m_g_list_index[array_size]           =list_index;
   m_g_item_text[array_size]            =item_text;
   m_g_item_index[array_size]           =item_index;
   m_g_node_level[array_size]           =node_level;
   m_g_prev_node_item_index[array_size] =prev_node_item_index;
   m_g_items_total[array_size]          =items_total;
   m_g_folders_total[array_size]        =folders_total;
   m_g_is_folder[array_size]            =is_folder;
  }

Durante el escaneo del sistema de archivos, cada vez que se encuentre la carpeta, hay que entrar en ella para ver su contenido. Para esta comprobación va a utilizarse el método CFileNavigator::IsFolder(), que ayudará a comprender si el elemento actual es una carpeta o un archivo. Es necesario pasar a este método el nombre del elemento del sistema de archivos como parámetro único. Si en el nombre del elemento se encuentra el signo de la barra inclinada invertida «\», eso significa que se trata de una carpeta, y el método devolverá true. Si es un archivo, el método devolverá false

Hay otra manera de comprobar si el elemento es un archivo: usando la función de sistema FileIsExist(). Esta función devuelve true si resulta que el nombre pasado del elemento en el handle recibido la última vez es un archivo. Si es una carpeta, la función genera el error ERR_FILE_IS_DIRECTORY.  

class CFileNavigator : public CElement
  {
private:
   //--- Determina si ha sido pasado el nombre de la carpeta o del archivo
   bool              IsFolder(const string file_name);
  };
//+------------------------------------------------------------------+
//| Determina si ha sido pasado el nombre de la carpeta o del archivo                         |
//+------------------------------------------------------------------+
bool CFileNavigator::IsFolder(const string file_name)
  {
//--- Si el nombre contiene los signos "\\", es una carpeta
   if(::StringFind(file_name,"\\",0)>-1)
      return(true);
//---
   return(false);
  }

Para la formación de la lista jerárquica hay que indicar el número de los elementos en el punto, así como el número de los elementos que son carpetas. Por eso vamos a necesitar los métodos correspondientes que ayudarán a determinar los valores para estos parámetros. Aquí, estos métodos serán CFileNavigator::ItemsTotal() y CFileNavigator::FoldersTotal(). La única diferencia entre ellos consiste en que el contador se aumenta en el segundo sólo si el elemento que se comprueba es la carpeta. Ambos métodos pasan dos argumentos: (1) la ruta y (2) el área de la búsqueda. Aquí, bajo el área de la búsqueda se supone la carpeta compartida de todos los terminales o la carpeta local del terminal. Luego, se utiliza la función de sistema FileFindFirst() para intentar obtener el manejador de la búsqueda según la ruta especificada y al mismo tiempo el nombre del primer elemento, si será encontrado. El manejador válido y el nombre del objeto servirán del indicio que el primer elemento ha sido encontrado. 

Luego, en el ciclo y usando la función FileFindNext(), se realiza el intento de obtener el acceso a todos los demás elementos de este directorio. La clave a este directorio es el manejador (handle) obtenido anteriormente. Si el elemento ha sido encontrado, la función devuelve true y el contador se aumenta. En cuanto la función devuelva false, la búsqueda se interrumpe. Hay que cerrar el handle de la búsqueda al final de ambos métodos. Para eso se utiliza la función de sistema FileFindClose()

class CFileNavigator : public CElement
  {
private:
   //--- Devuelve el número de (1) elementos y (2) carpetas en el directorio especificado
   int               ItemsTotal(const string search_path,const int mode);
   int               FoldersTotal(const string search_path,const int mode);
  };
//+------------------------------------------------------------------+
//| Calcula el número de archivos en el directorio actual                   |
//+------------------------------------------------------------------+
int CFileNavigator::ItemsTotal(const string search_path,const int search_area)
  {
   int    counter       =0;              // contador de elementos 
   string file_name     ="";             // nombre del archivo
   long   search_handle =INVALID_HANDLE; // handle de la búsqueda
//--- Obtenemos el primer archivo en el directorio actual
   search_handle=::FileFindFirst(search_path,file_name,search_area);
//--- Si el directorio no está vacío
   if(search_handle!=INVALID_HANDLE && file_name!="")
     {
      //--- Calculamos el número de objetos en el directorio actual
      counter++;
      while(::FileFindNext(search_handle,file_name))
         counter++;
     }
//--- Cerramos el handle de la búsqueda
   ::FileFindClose(search_handle);
   return(counter);
  }
//+------------------------------------------------------------------+
//| Calcula el número de carpetas en el directorio actual                     |
//+------------------------------------------------------------------+
int CFileNavigator::FoldersTotal(const string search_path,const int search_area)
  {
   int    counter       =0;              // contador de elementos 
   string file_name     ="";             // nombre del archivo
   long   search_handle =INVALID_HANDLE; // handle de la búsqueda
//--- Obtenemos el primer archivo en el directorio actual
   search_handle=::FileFindFirst(search_path,file_name,search_area);
//--- Si no está vacío, calculamos en el ciclo el número de objetos en el directorio actual
   if(search_handle!=INVALID_HANDLE && file_name!="")
     {
      //--- Si es una carpeta, aumentamos el contador
      if(IsFolder(file_name))
         counter++;
      //--- Recorremos la lista y calculamos otras carpetas
      while(::FileFindNext(search_handle,file_name))
        {
         if(IsFolder(file_name))
            counter++;
        }
     }
//--- Cerramos el handle de la búsqueda
   ::FileFindClose(search_handle);
   return(counter);
  }

Durante la recopilación de los parámetros de los elementos del sistema de archivos, habrá que obtener los índices locales de nodos anteriores. Para eso vamos a usar el método CFileNavigator::PrevNodeItemIndex(). Los argumentos que va recibir son los siguientes: (1) índice actual del elemento en el directorio raíz y (2) nivel actual del nodo. Los valores de ambos argumentos se controlan por los contadores en los ciclos externos de los métodos principales dentro de los cuales va a invocarse este método. 

class CFileNavigator : public CElement
  {
private:
   //--- Devuelve el índice local del nodo anterior respecto a los parámetros pasados
   int               PrevNodeItemIndex(const int root_index,const int node_level);
  };
//+------------------------------------------------------------------+
//| Devuelve el índice local del nodo anterior                     |
//| respecto a los parámetros pasados                               |
//+------------------------------------------------------------------+
int CFileNavigator::PrevNodeItemIndex(const int root_index,const int node_level)
  {
   int prev_node_item_index=0;
//--- Si no en el directorio raíz
   if(node_level>1)
      prev_node_item_index=m_l_item_index[node_level-1];
   else
     {
      //--- Si no es el primer elemento de la lista
      if(root_index>0)
         prev_node_item_index=m_l_item_index[node_level-1];
     }
//--- Devolvemos el índice local del nodo anterior
   return(prev_node_item_index);
  }

Cada vez que se encuentre una carpeta, el programa entra en ella, es decir va al siguiente nivel del nodo. Para eso se llama al método CFileNavigator::ToNextNode(). Algunos argumentos-parámetros se pasan por referencia para asegurar la posibilidad de controlar sus valores

Aquí, al principio del método, se forma la ruta para el cálculo de los elementos en este directorio. Luego, los parámetros del elemento se guardan por el índice del nodo actual en los arrays auxiliares. Después de eso, hay que obtener el índice del elemento del nodo anterior y añadir el elemento con los parámetros especificados a los arrays generales. Es decir, sólo aquí la carpeta en la que hemos entrado, se añade a los arrays con los parámetros preparados para ella.

Luego, el contador de los nodos se aumenta, y respecto a él se corrige el tamaño de los arrays auxiliares. A continuación, para el nuevo nodo es necesario guardar algunos parámetros: (1) ruta, (2) número de elementos y (3) número de carpetas. Al final del método, (1) el contador de índices locales se pone a cero y (2) el handle actual de la búsqueda se cierra. 

class CFileNavigator : public CElement
  {
private:
   //--- Paso al siguiente nodo
   void              ToNextNode(const int root_index,int list_index,int &node_level,
                                int &item_index,long &handle,const string item_text,const int search_area);
  };
//+------------------------------------------------------------------+
//| Paso al siguiente nodo                                        |
//+------------------------------------------------------------------+
void CFileNavigator::ToNextNode(const int root_index,int list_index,int &node_level,
                                int &item_index,long &handle,const string item_text,const int search_area)
  {
//--- Filtro de la búsqueda (* - comprobar todos los archivos/carpetas)
   string filter="*";
//--- Formamos la ruta
   string search_path=m_l_path[node_level]+item_text+filter;
//--- Obtenemos y guardamos los datos
   m_l_item_total[node_level]           =ItemsTotal(search_path,search_area);
   m_l_folders_total[node_level]        =FoldersTotal(search_path,search_area);
   m_l_item_text[node_level]            =item_text;
   m_l_item_index[node_level]           =item_index;
   m_l_prev_node_list_index[node_level] =list_index;
//--- Obtenemos el índice del elemento del nodo anterior
   int prev_node_item_index=PrevNodeItemIndex(root_index,node_level);
//--- Añadimos el elemento con datos especificados a los arrays generales
   AddItem(list_index,item_text,node_level,prev_node_item_index,
           item_index,m_l_item_total[node_level],m_l_folders_total[node_level],true);
//--- Aumentamos el contador de nodos
   node_level++;
//--- Aumentamos el tamaño del array a un elemento
   AuxiliaryArraysResize(node_level);
//--- Obtenemos y guardamos los datos
   m_l_path[node_level]          =m_l_path[node_level-1]+item_text;
   m_l_item_total[node_level]    =ItemsTotal(m_l_path[node_level]+filter,search_area);
   m_l_folders_total[node_level] =FoldersTotal(m_l_path[node_level]+item_text+filter,search_area);
//--- Poner a cero el contador de los índices locales
   item_index=0;
//--- Cerramos el handle de la búsqueda
   ::FileFindClose(handle);
  }

Ahora vamos a analizar el ciclo principal en el que van a realizarse las acciones principales. Por razones de conveniencia, este ciclo ha sido pasado al método separado CFileNavigator::FileSystemScan(). En este método se realiza la lectura del sistema de archivos del terminal, con la escritura de los parámetros de los elementos encontrados en los arrays generales. Luego, estos arrays van a utilizarse para la construcción de la lista jerárquica. El ciclo va a trabajar hasta que algoritmo no llegue al final de la lista, o el programa no se elimine del gráfico. 

El algoritmo trabaja de la siguiente manera. Al principio del ciclo se realiza la comprobación del inicio de la lista (el primer elemento de la lista) del directorio actual. Si el elemento que se comprueba es realmente el primero, obtenemos el handle y el nombre del primer elemento encontrado en la zona especificada del sistema de archivos, y guardamos el número de los elementos y archivos en los arrays auxiliares, en el índice del nivel actual del nodo. 

Si no es el primer índice, se comprueba la secuencia de los índices locales en el nodo actual. Si el índice de este nodo se repite, el contador de los índices locales se aumenta y se realiza el paso al siguiente elemento en el directorio actual.

Si el algoritmo ha llegado al siguiente bloque del código, hay que comprobar si salimos de los límites de la lista de elementos perteneciente al nodo raíz. Si es así, el ciclo se detiene, lo que también supone el cierre del handle de la búsqueda (ya fuera del bloque del ciclo) y la salida del programa del método. Si hemos llegado al final de la lista de cualquier otro nodo, excepto el nodo raíz, hay que pasar al nivel más alto. Aquí, (1) el contador de nodos se disminuye a un nivel atrás, (2) se pone a cero el contador de los índices locales, (3) se cierra el handle de la búsqueda y (4) se pasa a la siguiente iteración del ciclo.

Si ninguna condición en la construcción anterior if-else se ha cumplido, primero se comprueba si el elemento actual del sistema de archivos es una carpeta. Si es así, se realiza el paso al siguiente nivel usando el método CFileNavigator::ToNextNode() analizado anteriormente. Luego, se aumenta el contador de índices generales y se da la orden para ir a la siguiente iteración. 

Si resulta que el elemento del sistema de archivos es un archivo, primero obtenemos el índice local del nodo anterior. Luego, añadimos el elemento con parámetros especificados a los arrays generales. Aumentamos el contador de los índices generales y el contador de los índices locales. Y como operación final del ciclo, se realiza el paso al siguiente elemento del sistema de archivos del terminal. Después de eso, se empieza la siguiente iteración del ciclo y el algoritmo repasa todas las condiciones arriba descritas. 

class CFileNavigator : public CElement
  {
private:
   //--- Lee el sistema de archivos y escribe los parámetros en los arrays
   void              FileSystemScan(const int root_index,int &list_index,int &node_level,int &item_index,int search_area);
  };
//+------------------------------------------------------------------+
//| Lee el sistema de archivos del terminal y escribe                   |
//| los parámetros de los elementos en los arrays                                    |
//+------------------------------------------------------------------+
void CFileNavigator::FileSystemScan(const int root_index,int &list_index,int &node_level,int &item_index,int search_area)
  {
   long   search_handle =INVALID_HANDLE; // Handle de la búsqueda de la carpeta/archivo
   string file_name     ="";             // Nombre del elemento encontrado (archivo/carpeta)
   string filter        ="*";            // Filtro de la búsqueda (* - comprobar todos los archivos/carpetas)
//--- Escaneamos los directorios e introducimos los datos en los arrays
   while(!::IsStopped())
     {
      //--- Si es el inicio de la lista del directorio
      if(item_index==0)
        {
         //--- Ruta para la búsqueda de todos los elementos
         string search_path=m_l_path[node_level]+filter;
         //--- Obtenemos el handle y el nombre del primer archivo
         search_handle=::FileFindFirst(search_path,file_name,search_area);
         //--- Obtenemos el número de archivos y carpetas en el directorio especificado
         m_l_item_total[node_level]    =ItemsTotal(search_path,search_area);
         m_l_folders_total[node_level] =FoldersTotal(search_path,search_area);
        }
      //--- Si el índice de este nodo se repite, pasamos al siguiente archivo
      if(m_l_item_index[node_level]>-1 && item_index<=m_l_item_index[node_level])
        {
        //--- Aumentamos el contador de índices locales
         item_index++;
         //--- Vamos al siguiente elemento
         ::FileFindNext(search_handle,file_name);
         continue;
        }
      //--- Si hemos llegado al final de la lista en el nodo raíz, terminamos el ciclo
      if(node_level==1 && item_index>=m_l_item_total[node_level])
         break;
      //--- Si hemos llegado al final de la lista en cualquier nodo, salvo el nodo raíz
      else if(item_index>=m_l_item_total[node_level])
        {
         //--- Pasar el contador de nodos a un nivel atrás
         node_level--;
         //--- Poner a cero el contador de los índices locales
         item_index=0;
         //--- Cerramos el handle de la búsqueda
         ::FileFindClose(search_handle);
         continue;
        }
      //--- Si es una carpeta
      if(IsFolder(file_name))
        {
         //--- Vamos al siguiente nodo
         ToNextNode(root_index,list_index,node_level,item_index,search_handle,file_name,search_area);
         //--- Aumentar el contador de índices generales y empezar nueva iteración
         list_index++;
         continue;
        }
      //--- Obtenemos el índice local del nodo anterior
      int prev_node_item_index=PrevNodeItemIndex(root_index,node_level);
      //--- Añadimos el elemento con datos especificados a los arrays generales
      AddItem(list_index,file_name,node_level,prev_node_item_index,item_index,0,0,false);
       //--- Aumentamos el contador de índices generales
      list_index++;
      //--- Aumentamos el contador de índices locales
      item_index++;
      //--- Vamos al siguiente elemento
      ::FileFindNext(search_handle,file_name);
     }
//--- Cerramos el handle de la búsqueda
   ::FileFindClose(search_handle);
  }

Ahora veremos el método principal CFileNavigator::FillArraysData(), en el que se invocan todos los métodos descritos anteriormente.

En primer lugar, aquí se establece la secuencia de directorios de la carpeta común y local del terminal. Esta secuencia depende del modo (ENUM_FILE_NAVIGATOR_CONTENT) establecido en las propiedades del explorador de archivos. Por defecto, la secuencia se establece de tal manera que la carpeta común del terminal sea la primera en la lista, y la carpeta local del terminal sea la segunda. Eso funciona sólo si ha sido establecido el modo FN_BOTH (“mostrar el contenido de ambos directorios”). Si ha sido seleccionado el modo “mostrar el contenido de un directorio”, el inicio (begin) y el fin (end) del rango del ciclo serán inicializados de la manera correspondiente.

Una vez determinada el área de la búsqueda en el inicio del cuerpo del ciclo, se realizan paso a paso las siguientes acciones.

  • Se pone a cero el contador de índices locales
  • El tamaño de los arrays auxiliares se cambia en función del nivel del nodo actual
  • En el primer índice de los mismos arrays se guarda el número de los elementos y carpetas en el directorio actual
  • El elemento con parámetros especificados se añade a los arrays principales. Puesto que aquí es el directorio raíz, el nombre para el elemento actual puede ser para uno de dos directorios: "Common\\Files\\" o "MQL5\\Files\\"
  • Se aumentan los contadores de índices generales y niveles de los nodos
  • De nuevo se cambia el tamaño de los arrays auxiliares en función del valor actual del nivel del nodo
  • Si ahora el área de la búsqueda se encuentra en la carpeta local del terminal, se corrigen los valores para los primeros índices de los arrays auxiliares: (1) índices locales e (2) índices generales del nodo anterior

Finalmente, se invoca el método CFileNavigator::FileSystemScan() para la lectura del sistema de archivos en el área especificada de la búsqueda y el guardado de parámetros de sus elementos en los arrays principales que sirven para la formación de la lista jerárquica. 

class CFileNavigator : public CElement
  {
private:
   //--- Llena los arrays con parámetros de los elementos del sistema de archivos del terminal
   void              FillArraysData(void);
  };
//+------------------------------------------------------------------+
//| Llena los arrays con parámetros de los elementos del sistema de archivos         |
//+------------------------------------------------------------------+
void CFileNavigator::FillArraysData(void)
  {
//--- Contadores de (1) índices generales, (2) niveles de los nodos e (3) índices locales
   int list_index =0;
   int node_level =0;
   int item_index =0;
//--- Si es necesario mostrar ambos directorios (Común (0)/Local (1))
   int begin=0,end=1;
//--- Si es necesario mostrar el contenido sólo del directorio local
   if(m_navigator_content==FN_ONLY_MQL)
      begin=1;
//--- Si es necesario mostrar el contenido sólo del directorio común
   else if(m_navigator_content==FN_ONLY_COMMON)
      begin=end=0;
//--- Recorremos los directorios indicados
   for(int root_index=begin; root_index<=end; root_index++)
     {
      //--- Determinamos el directorio para el escaneo de la estructura de archivos
      int search_area=(root_index>0)? 0 : FILE_COMMON;
      //--- Ponemos a cero el contador de los índices locales
      item_index=0;
      //--- Aumentamos el tamaño de los arrays auxiliares a un elemento (respecto al nivel del nodo)
      AuxiliaryArraysResize(node_level);
       //--- Obtenemos el número de archivos y carpetas en el directorio especificado (* - comprobar todos los archivos/carpetas)
      string search_path   =m_l_path[0]+"*";
      m_l_item_total[0]    =ItemsTotal(search_path,search_area);
      m_l_folders_total[0] =FoldersTotal(search_path,search_area);
      //--- Añadimos el elemento con el nombre del directorio raíz
      string item_text=(root_index>0)? "MQL5\\Files\\" : "Common\\Files\\";
      AddItem(list_index,item_text,0,0,root_index,m_l_item_total[0],m_l_folders_total[0],true);
      //--- Aumentamos los contadores de índices generales y niveles de los nodos
      list_index++;
      node_level++;
      //--- Aumentamos el tamaño de los arrays a un elemento (respecto al nivel del nodo)
      AuxiliaryArraysResize(node_level);
      //--- Inicialización de los primeros elementos para el directorio de la carpeta local del terminal
      if(root_index>0)
        {
         m_l_item_index[0]           =root_index;
         m_l_prev_node_list_index[0] =list_index-1;
        }
      //--- Escaneamos los directorios e introducimos los datos en los arrays
      FileSystemScan(root_index,list_index,node_level,item_index,search_area);
     }
  }

 


Métodos para la creación del control

La llamada al método CFileNavigator::FillArraysData() se realiza sólo una vez, antes de la creación del control “Explorador de archivos”. En realidad, el usuario de la librería incluso puede no saber de él y desconocer cómo está organizado. Será suficiente especificar algunas propiedades generales que influyen en la apariencia y el contenido del explorador de archivos.

//+------------------------------------------------------------------+
//| Crea el explorador de archivos                                       |
//+------------------------------------------------------------------+
bool CFileNavigator::CreateFileNavigator(const long chart_id,const int subwin,const int x,const int y)
  {
//--- Salir si no hay puntero al formulario
   if(::CheckPointer(m_wnd)==POINTER_INVALID)
     {
      ::Print(__FUNCTION__," > Antes de crear el explorador de archivos, hay que pasarle "
              "el puntero al formulario: CFileNavigator::WindowPointer(CWindow &object).");
      return(false);
     }
//--- Escaneamos el sistema de archivos del terminal e introducimos los datos en los arrays
   FillArraysData();
//--- 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árgenes desde el punto extremo
   CElement::XGap(CElement::X()-m_wnd.X());
   CElement::YGap(CElement::Y()-m_wnd.Y());
//--- Creación del control
   if(!CreateAddressBar())
      return(false);
   if(!CreateTreeView())
      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);
  }

La creación de la barra de direcciones es el primer paso a la hora de diseñar el explorador de archivos. Será un objeto tipo OBJ_BITMAP_LABEL, cuyo contenido será completamente dibujado. Antes ya hemos mostrado los ejemplos de la creación de los controles dibujados sobre el lienzo. Por eso vamos a evitar repeticiones y nos centramos en los momentos que se refieren al control que estamos desarrollando. 

Para el dibujo de la barra de direcciones nos harán falta dos métodos:

  • Método CFileNavigator::Border() – se utiliza para el dibujo del borde de la barra de direcciones.
  • Método CFileNavigator::UpdateAddressBar() – el método principal para el dibujo y visualización de los últimos cambios a los que se refiere el directorio seleccionado en la lista jerárquica.

Vamos a mostrar aquí sólo el código del método CFileNavigator::UpdateAddressBar(), porque ya hemos visto el trazado del borde en el ejemplo de otros controles. Por ejemplo, en el artículo Interfaces gráficas IV: Elementos informativos de la interfaz (Capítulo 1) 

Antes ya hemos mencionado que el usuario tiene la posibilidad de especificar el color del fondo de la barra de direcciones y su tamaño por el eje Y, antes de la creación del explorador de archivos. El texto en el área del lienzo para el dibujo va a posicionarse con el margen de 5 píxeles por el eje X desde el borde izquierdo, mientras que la ubicación por el eje Y debe ser centrada. Teniendo el valor del tamaño por el eje Y, para obtener la coordenada Y, simplemente hay que dividir el alto de la barra de direcciones por 2. Durante la llamada al método TextOut() para el dibujo del texto sobre el lienzo, es importante pasarle las banderas para el modo del anclaje a la izquierda y por el centro

Durante la primera instalación del explorador de archivos, la ruta todavía no está inicializada y el campo de la clase m_current_path destinado para su almacenamiento contiene la línea vacía. Puesto que pueden haber muchos elementos del sistema de archivos, la formación de los arrays y la creación de la lista jerárquica puede tardar algún tiempo. Dado que la barra de direcciones se crea la primera, ahí se puede mostrar el texto que avisa sobre la necesidad de esperar un poco. Por ejemplo, aquí será el texto "Loading. Please wait...". 

Al final del método, el lienzo se actualiza para mostrar los últimos cambios realizados. 

class CFileNavigator : public CElement
  {
private:
   //--- Dibuja el marco para la barra de direcciones
   void              Border(void);
   //--- Muestra la ruta actual en la barra de direcciones
   void              UpdateAddressBar(void);
  };
//+------------------------------------------------------------------+
//| Muestra la ruta actual en la barra de direcciones                        |
//+------------------------------------------------------------------+
void CFileNavigator::UpdateAddressBar(void)
  {
//--- Coordenadas
   int x=5;
   int y=m_address_bar_y_size/2;
//--- Limpiar el fondo
   m_address_bar.Erase(::ColorToARGB(m_address_bar_back_color,0));
//--- Dibujar el marco del fondo
   Border();
//--- Propiedades del texto
   m_address_bar.FontSet("Calibri",14,FW_NORMAL);
//--- Si la ruta aún no ha sido establecida, mostrar la línea predefinida
   if(m_current_full_path=="")
      m_current_full_path="Loading. Please wait...";
//--- Mostramos la ruta en la barra de direcciones del explorador de archivos
   m_address_bar.TextOut(x,y,m_current_path,::ColorToARGB(m_address_bar_text_color),TA_LEFT|TA_VCENTER);
//--- Actualizamos el lienzo para el dibujo
   m_address_bar.Update();
  }

El ancho de la barra de direcciones se calcula antes de su creación en el método CFileNavigator::CreateAddressBar(). Si en los ajustes se establece que el área del contenido está desactivada, el ancho de la barra de direcciones será igual al ancho de la lista jerárquica. En otras ocasiones, se calcula según el mismo principio que ha sido implementado en la clase de la lista jerárquica (CTreeView) para el ancho general del control.

Una vez creado el objeto, se llama al método CFileNavigator::UpdateAddressBar() para dibujar el fondo, el borde y la muestra del mensaje predefinido. 

//+------------------------------------------------------------------+
//| Crea la barra de direcciones                                          |
//+------------------------------------------------------------------+
bool CFileNavigator::CreateAddressBar(void)
  {
//--- Formación del nombre del objeto
   string name=CElement::ProgramName()+"_file_navigator_address_bar_"+(string)CElement::Id();
//--- Coordenadas
   int x =CElement::X();
   int y =CElement::Y();
//--- Tamaños:
    //--- Calculamos el ancho
   int x_size=0;
//--- Si no habrá el área del contenido
   if(m_content_area_width<0)
      x_size=m_treeview_area_width;
   else
     {
      //--- Si se indica un determinado ancho del área del contenido
      if(m_content_area_width>0)
         x_size=m_treeview_area_width+m_content_area_width-1;
      //--- Si el lado derecho del área del contenido debe encontrarse al lado del borde derecho del formulario
      else
         x_size=m_wnd.X2()-x-2;
     }
//--- Alto
   int y_size=m_address_bar_y_size;
//--- Creación del objeto
   if(!m_address_bar.CreateBitmapLabel(m_chart_id,m_subwin,name,x,y,x_size,y_size,COLOR_FORMAT_XRGB_NOALPHA))
      return(false);
//--- Adjuntar al gráfico
   if(!m_address_bar.Attach(m_chart_id,name,m_subwin,1))
      return(false);
//--- Establecemos las propiedades
   m_address_bar.Background(false);
   m_address_bar.Z_Order(m_zorder);
   m_address_bar.Tooltip("\n");
//--- Guardamos los tamaños
   CElement::X(x);
   CElement::Y(y);
//--- Guardamos los tamaños
   CElement::XSize(x_size);
   CElement::YSize(y_size);
//--- Márgenes desde el punto extremo
   m_address_bar.XGap(x-m_wnd.X());
   m_address_bar.YGap(y-m_wnd.Y());
//--- Actualizar la barra de direcciones
   UpdateAddressBar();
//--- Guardamos el puntero del objeto
   CElement::AddToArray(m_address_bar);
//--- Ocultar el elemento si es la ventana de diálogo o está minimizada
   if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized())
      m_address_bar.Timeframes(OBJ_NO_PERIODS);
//---
   return(true);
  }

Hemos llegado al momento cuando durante la creación del explorador de archivos se llama al método CFileNavigator::CreateTreeView() para establecer la lista jerárquica. Del artículo anterior debemos recordar que antes de crear el control tipo CTreeView, primero hay que añadir los elementos indicando sus parámetros a los arrays de este control. En esta fase, todos los parámetros para los elementos se encuentran en los arrays principales de la clase CFileNavigator. Nos queda pasarlos en el ciclo a la clase de la lista jerárquica

La imagen para cada elemento va a determinarse en el mismo ciclo. Aparte de eso, hay que eliminar el signo (‘\’) de los nombres de los elementos que son archivos. 

//+------------------------------------------------------------------+
//| Crea la lista jerárquica                                      |
//+------------------------------------------------------------------+
bool CFileNavigator::CreateTreeView(void)
  {
//--- Guardamos el puntero a la ventana
   m_treeview.WindowPointer(m_wnd);
//--- Establecemos las propiedades
   m_treeview.Id(CElement::Id());
   m_treeview.XSize(CElement::XSize());
   m_treeview.YSize(CElement::YSize());
   m_treeview.ResizeListAreaMode(true);
   m_treeview.TreeViewAreaWidth(m_treeview_area_width);
   m_treeview.ContentAreaWidth(m_content_area_width);
//--- Formamos los arrays  de la lista jerárquica
   int items_total=::ArraySize(m_g_item_text);
   for(int i=0; i<items_total; i++)
     {
      //--- Determinamos la imagen para el elemento (carpeta/archivo)
      string icon_path=(m_g_is_folder[i])? m_folder_icon : m_file_icon;
      //--- Si es carpeta, eliminamos el último signo ('\') en la línea 
      if(m_g_is_folder[i])
         m_g_item_text[i]=::StringSubstr(m_g_item_text[i],0,::StringLen(m_g_item_text[i])-1);
       //--- Añadimos el elemento a la lista jerárquica
      m_treeview.AddItem(i,m_g_prev_node_list_index[i],m_g_item_text[i],icon_path,m_g_item_index[i],
                         m_g_node_level[i],m_g_prev_node_item_index[i],m_g_items_total[i],m_g_folders_total[i],false,m_g_is_folder[i]);
     }
//--- Crear la lista jerárquica
   if(!m_treeview.CreateTreeView(m_chart_id,m_subwin,m_x,m_y+m_address_bar_y_size))
      return(false);
//---
   return(true);
  }

 

 


Manejador de eventos

En los campos de la clase va a guardarse (1) la ruta completa del directorio seleccionado en la lista jerárquica en relación al sistema de archivos, incluyendo la etiqueta del tomo del disco duro, así como (2) la ruta en relación a la “zona protegida” del terminal, y (3) el área del directorio actual. Para obtener estos valores, hay que usar los métodos correspondientes. Aparte de eso, necesitaremos el método CFileNavigator::SelectedFile() para obtener el elemento seleccionado que es archivo.

class CFileNavigator : public CElement
  {
private:
   //--- Ruta actual respecto a la “zona protegida” del terminal
   string            m_current_path;
   //--- Ruta completa en relación al sistema de archivos, incluyendo la etiqueta del tomo del disco duro
   string            m_current_full_path;
   //--- Área del directorio actual
   int               m_directory_area;
   //---
public:
   //--- Devuelve (1) la ruta actual y (2) la ruta completa, (3) archivo seleccionado
   string            CurrentPath(void)                                  const { return(m_current_path);                    }
   string            CurrentFullPath(void)                              const { return(m_current_full_path);               }
   //--- Devuelve (1) el área de los directorios y (2) el archivo seleccionado
   int               DirectoryArea(void)                                const { return(m_directory_area);                  }
   string            SelectedFile(void)                                 const { return(m_treeview.SelectedItemFileName()); }
  };

El manejador de eventos del explorador de archivos estará configurado a la recepción de un solo evento con el identificador ON_CHANGE_TREE_PATH. Es generado por la lista jerárquica cuando se selecciona uno u otro elemento en su estructura. Para el procesamiento del mensaje con este identificador, ha sido implementado el método CFileNavigator::OnChangeTreePath(). 

Aquí, primero obtenemos la ruta almacenada en la lista jerárquica. Luego, dependiendo de la categoría a la que pertenece esta ruta (común o local), (1) obtenemos la dirección del directorio raíz de los datos y (2) formamos la ruta reducida y completa, y guardamos la bandera del área del directorio

class CFileNavigator : public CElement
  {
private:
   //--- Procesamiento del evento de la selección de nueva ruta en la lista jerárquica
   void              OnChangeTreePath(void);
  };
//+------------------------------------------------------------------+
//| Procesamiento del evento de la selección de nueva ruta en la lista jerárquica        |
//+------------------------------------------------------------------+
void CFileNavigator::OnChangeTreePath(void)
  {
//--- Obtenemos la ruta actual
   string path=m_treeview.CurrentFullPath();
//--- Es la carpeta compartida de los terminales
   if(::StringFind(path,"Common\\Files\\",0)>-1)
     {
      //--- Obtenemos la dirección de la carpeta compartida de los terminales
      string common_path=::TerminalInfoString(TERMINAL_COMMONDATA_PATH);
      //--- Eliminamos el prefijo "Common\" en la línea (obtenida en el evento)
      path=::StringSubstr(path,7,::StringLen(common_path)-7);
      //--- Formamos la ruta (versión reducida y completa)
      m_current_path      =::StringSubstr(path,6,::StringLen(path)-6);
      m_current_full_path =common_path+"\\"+path;
      //--- Guardamos el área del directorio
      m_directory_area=FILE_COMMON;
     }
//--- Si es la carpeta local del terminal
   else if(::StringFind(path,"MQL5\\Files\\",0)>-1)
     {
      //--- Obtenemos la dirección en la carpeta local del terminal
      string local_path=::TerminalInfoString(TERMINAL_DATA_PATH);
      //--- Formamos la ruta (versión reducida y completa)
      m_current_path      =::StringSubstr(path,11,::StringLen(path)-11);
      m_current_full_path =local_path+"\\"+path;
      //--- Guardamos el área del directorio
      m_directory_area=0;
     }
//--- Mostramos la ruta actual en la barra de direcciones
   UpdateAddressBar();
  }

 

En conclusión, cuando llega el evento con el identificador ON_CHANGE_TREE_PATH, en el manejador de eventos del elemento hay que llamar al método CFileNavigator::OnChangeTreePath(), como se muestra en el código de abajo:

//+------------------------------------------------------------------+
//| Manejador de eventos                                               |
//+------------------------------------------------------------------+
void CFileNavigator::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Procesamiento del evento “Cambio de la ruta en la lista jerárquica”
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE_TREE_PATH)
     {
      OnChangeTreePath();
      return;
     }
  }

También se puede recibir el evento con el mismo identificador en el manejador de la clase personalizada. El ejemplo está más abajo.

 


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

Para un correcto funcionamiento del control, hay que integrarlo en el motor de la librería. Las adiciones deben introducirse principalmente en la clase base CWndContainer que se encuentra en el archivo WndContainer.mqh, en el que se incluyen los archivos de todos los demás controles de la librería. Es necesario agregar: 

  • array personal para el explorador de archivos;
  • método para obtener el número de exploradores de archivos de este tipo en la interfaz gráfica (CFileNavigator);
  • método para guardar los punteros a los controles del explorador de archivos en la base.

A continuación, se muestra la versión reducida de la clase CWndContainer (sólo lo que es necesario añadir):

#include "FileNavigator.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
     {
      //--- Explorador de archivos
      CFileNavigator   *m_file_navigators[];
     };
   //--- Array de los arrays de los controles para cada ventana
   WindowElements    m_wnd[];
   //---
public:
   //--- Número de los explorador de archivos
   int               FileNavigatorsTotal(const int window_index);
   //---
private:
  //--- Guarda los punteros a los controles de la lista jerárquica en la base
   bool              AddFileNavigatorElements(const int window_index,CElement &object);
  };

Usted puede estudiar el código de estos métodos más detenidamente en los archivos adjuntos al artículo. 



Prueba del explorador de archivos

Pues, tenemos todo preparado para probar el explorador de archivos. Para la prueba vamos a usar el Asesor Experto del artículo anterior. Hagamos su copia y eliminamos de la clase personalizada (CProgram) todos los controles, salvo el menú principal y la barra de estado. Para probar rápidamente los modos principales del contenido del explorador de archivos, hagamos dos parámetros externos en el EA, tal como se muestra en el código de abajo. Hemos considerado detalladamente estos tipos de enumeraciones y modos antes en este artículo.

//+------------------------------------------------------------------+
//|                                                      Program.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//--- Parámetros externos
input ENUM_FILE_NAVIGATOR_CONTENT NavigatorContent =FN_BOTH;         // Navigator content
input ENUM_FILE_NAVIGATOR_MODE    NavigatorMode    =FN_ONLY_FOLDERS; // Navigator mode

Ahora hay que declarar la instancia de la clase del explorador de archivos CFileNavigator, así como el método para la creación del control, y los márgenes desde el punto extremo del formulario al que será adjuntado el control. 

class CProgram : public CWndEvents
  {
private:
   //--- Explorador de archivos
   CFileNavigator    m_navigator;
   //---
private:
   //--- Explorador de archivos
#define NAVIGATOR1_GAP_X      (2)
#define NAVIGATOR1_GAP_Y      (43)
   bool              CreateFileNavigator(void);
  };

El código del método CProgram::CreateFileNavigator() para la creación del explorador de archivos se muestra más abajo. Establecemos la altura para el explorador en 10 elementos, cuyo tamaño ha sido establecido por defecto (20 píxeles) en el constructor de la clase CFileNavigator. Como ya hemos mencionado antes, los modos del contenido van a controlarse con los parámetros externos. La apariencia de las partes integrantes se ajusta con los métodos que se puede obtener por el puntero. En el código de abajo, eso se demuestra en el ejemplo de la obtención de punteros de la lista jerárquica y sus barras de desplazamiento. La llamada al método debe ubicarse en el método principal de la creación de la interfaz gráfica

//+------------------------------------------------------------------+
//| Crea el panel del EA                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateExpertPanel(void)
  {
//--- Creación del formulario 1 para los controles
//--- Creación de controles:
//    Menú principal
//--- Menús contextuales
//--- Creación de la barra de estado
//--- Creación del explorador de archivos
   if(!CreateFileNavigator())
      return(false);
 //--- Redibujar el gráfico
   m_chart.Redraw();
   return(true);
  }
//+------------------------------------------------------------------+
//| Crea el explorador de archivos                                       |
//+------------------------------------------------------------------+
bool CProgram::CreateFileNavigator(void)
  {
//--- Guardamos el puntero al formulario
   m_navigator.WindowPointer(m_window1);
//--- Coordenadas
   int x=m_window1.X()+NAVIGATOR1_GAP_X;
   int y=m_window1.Y()+NAVIGATOR1_GAP_Y;
//--- Establecemos las propiedades antes de la creación
   m_navigator.TreeViewPointer().VisibleItemsTotal(10);
   m_navigator.NavigatorMode(NavigatorMode);
   m_navigator.NavigatorContent(NavigatorContent);
   m_navigator.TreeViewAreaWidth(250);
   m_navigator.AddressBarBackColor(clrWhite);
   m_navigator.AddressBarTextColor(clrSteelBlue);
//--- Propiedades de las barras de desplazamiento
   m_navigator.TreeViewPointer().GetScrollVPointer().AreaBorderColor(clrLightGray);
   m_navigator.TreeViewPointer().GetContentScrollVPointer().AreaBorderColor(clrLightGray);
//--- Creación del control
   if(!m_navigator.CreateFileNavigator(m_chart_id,m_subwin,x,y))
      return(false);
//--- Añadimos el puntero al control a la base
   CWndContainer::AddToElementsArray(0,m_navigator);
   return(true);
  }

Como ejemplo, hagamos en el manejador de eventos que en el registro aparezca la ruta breve y completa, así como el nombre del archivo seleccionado en este momento. Si el archivo está seleccionado, vamos a abrirlo y leer tres primeras líneas, con la visualización en el registro. Fíjese que para el control del área del directorio, nosotros usamos el método CFileNavigator::DirectoryArea() para obtener la bandera correspondiente a la ubicación del archivo.  

//+------------------------------------------------------------------+
//| Manejador de eventos                                               |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Procesamiento del evento “Cambio de la ruta en la lista jerárquica”
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE_TREE_PATH)
     {
      ::Print(__FUNCTION__," > id: ",id,"; file name: ",m_navigator.SelectedFile());
      ::Print(__FUNCTION__," > id: ",id,"; path: ",m_navigator.CurrentPath()+m_navigator.SelectedFile());
      ::Print(__FUNCTION__," > id: ",id,"; full path: ",m_navigator.CurrentFullPath()+m_navigator.SelectedFile());
      //--- Si el archivo está seleccionado, vamos a leerlo (tres primeras líneas)
      if(m_navigator.SelectedFile()!="")
        {
         //--- Vamos a formar la ruta hacia el archivo
         string path=m_navigator.CurrentPath()+m_navigator.SelectedFile();
         //--- Obtenemos el handle del archivo especificado
         int filehandle=::FileOpen(path,FILE_READ|FILE_TXT|FILE_ANSI|m_navigator.DirectoryArea(),'\n');
         //--- Si el handle ha sido recibido, leemos tes primeras líneas
         if(filehandle!=INVALID_HANDLE)
           {
            ::Print(__FUNCTION__," > Открыт файл: ",path);
            ::Print(__FUNCTION__," > Строка 01: ",::FileReadString(filehandle));
            ::Print(__FUNCTION__," > Строка 02: ",::FileReadString(filehandle));
            ::Print(__FUNCTION__," > Строка 03: ",::FileReadString(filehandle));
           }
         //--- Cerramos el archivo
         ::FileClose(filehandle);
        }
      ::Print("---");
     }
  }

Sólo nos queda compilar el programa y cargarlo en el gráfico. El resultado se muestra en la captura de pantalla de abajo. En su caso, el contenido del explorador de archivos debe corresponder al contenido del sistema de archivos del terminal en su ordenador.

 Fig. 1. Prueba del explorador de archivos.

Fig. 1. Prueba del explorador de archivos.


En la captura de pantalla de abajo se muestra otro ejemplo con la estructura expandida de la lista jerárquica del explorador de archivos: 

 Fig. 2. Estructura expandida de la lista jerárquica del explorador de archivos.

Fig. 2. Estructura expandida de la lista jerárquica del explorador de archivos.


En caso con el procesamiento del evento en la clase personalizada que ha sido establecido en nuestro EA, cuando se selecciona un archivo, en el registro va a mostrarse el siguiente resultado: 

2016.06.16 02:15:29.994         CProgram::OnEvent > Línea 03: 2,155.66,1028.00,1.04,0.30,0.64,0.24,0.01,2,0,10,10,0
2016.06.16 02:15:29.994         CProgram::OnEvent > Línea 02: 1,260.67,498.00,1.13,1.05,0.26,1.00,0.03,3,0,10,10,0
2016.06.16 02:15:29.994         CProgram::OnEvent > Línea 01: №,PROFIT,TOTAL DEALS,PROFIT FACTOR,EXPECTED PAYOFF,EQUITY DD MAX REL%,RECOVERY FACTOR,SHARPE RATIO,AmountBars,TakeProfit,StopLoss,TrailingSL,ReversePosition
2016.06.16 02:15:29.994         CProgram::OnEvent > Archivo abierto: DATA_OPTIMIZATION\WriteResOptByCriterion\optimization_results2.csv
2016.06.16 02:15:29.994         CProgram::OnEvent > id: 1023; full path: C:\Users\tol64\AppData\Roaming\MetaQuotes\Terminal\Common\Files\DATA_OPTIMIZATION\WriteResOptByCriterion\optimization_results2.csv
2016.06.16 02:15:29.994         CProgram::OnEvent > id: 1023; path: DATA_OPTIMIZATION\WriteResOptByCriterion\optimization_results2.csv
2016.06.16 02:15:29.994         CProgram::OnEvent > id: 1023; file name: optimization_results2.csv

¡Todo funciona tal como lo hemos planeado!  

 

 


Conclusión

En esta fase del desarrollo de la librería para la creación de las interfaces gráficas, su esquema tiene el siguiente aspecto

Fig. 3. Estructura de la librería en la fase actual del desarrollo. 

Fig. 3. Estructura de la librería en la fase actual del desarrollo.

Hemos terminado la octava parte de la serie sobre la creación de las interfaces gráficas en los terminales de trading MetaTrader. En esta parte hemos analizado los controles como el calendario estático y desplegable, lista jerárquica, puntero para el cursor del ratón y el explorador de archivos. 

En la siguiente (novena) parte de la serie hablaremos sobre los siguientes controles:

  • Controles para seleccionar el color.
  • Indicador de progreso.
  • Gráfico lineal.

Más abajo puede descargar el material de la octava 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/2541

Archivos adjuntos |
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.
Interfaces gráficas VIII: Control "Lista jerárquica" (Capítulo 2) Interfaces gráficas VIII: Control "Lista jerárquica" (Capítulo 2)
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.
Aplicación de lógica difusa en el trading por medio del MQL4 Aplicación de lógica difusa en el trading por medio del MQL4
El artículo se refiere a ejemplos de la aplicación de teoría de conjuntos difusa en el trading por medio del MQL4. El uso en el desarrollo de la librería FuzzyNet del MQL4 en un indicador y un Asesor Experto se describe así.
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.