
Interfaces gráficas VIII: Control "Explorador de archivos" (Capítulo 3)
Índice
- Introducción
- Control “Explorador de archivos”
- Desarrollo de la clase CFileNavigator
- Formación de la estructura jerárquica del sistema de archivos
- Métodos para la creación del control
- Manejador de eventos
- Integración del control en el motor de la librería
- Prueba del explorador de archivos
- Conclusión
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.
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.
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.
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:
- Interfaces gráficas VIII: Control “Calendario” (Capítulo 1)
- Interfaces gráficas VIII: Control “Lista jerárquica” (Capítulo 2)
- Interfaces gráficas VIII: Control "Explorador de archivos" (Capítulo 3)
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/2541





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso