English 中文 Español Deutsch 日本語 Português
Графические интерфейсы VIII: Элемент "Файловый навигатор" (Глава 3)

Графические интерфейсы VIII: Элемент "Файловый навигатор" (Глава 3)

MetaTrader 5Примеры | 6 июля 2016, 11:56
2 725 53
Anatoli Kazharski
Anatoli Kazharski

Содержание

 


Введение

Более подробно о том, для чего предназначена эта библиотека, можно прочитать в самой первой статье: Графические интерфейсы I: Подготовка структуры библиотеки (Глава 1). В конце статей каждой части представлен список глав со ссылками. Там же есть возможность загрузить к себе на компьютер полную версию библиотеки на текущей стадии разработки. Файлы нужно разместить по тем же директориям, как они расположены в архиве.

В первой и второй главе восьмой части серии библиотека пополнилась несколькими классами для создания указателей для курсора мыши (CPointer), календарей (классы CCalendar и CDropCalendar) и древовидных списков (классы CTreeItem и CTreeView). В этой статье  мы разовьем тему предыдущей главы и рассмотрим элемент «Файловый навигатор». 

 


Элемент "Файловый навигатор"

Файловый навигатор — это своего рода путеводитель, который позволяет увидеть элементы файловой системы в иерархическом представлении с помощью графического интерфейса используемой программы. Также он дает доступ к каждому элементу этой иерархии и произвести над ним какие-либо действия: открыть файл для просмотра данных, сохранить данные в файл, переместить файл и т.д.

В этой статье рассмотрим первую версию файлового навигатора. Он даст пользователю следующие возможности:

  • перемещаться в файловой «песочнице» терминала в рамках графического интерфейса MQL-приложения;
  • находить нужные для работы папки и файлы в общей папке терминалов и локальной папке клиентского терминала;
  • сохранять путь для последующего программного доступа к выделенной в файловом навигаторе папке или конкретному файлу. 

Примечание из справки:

Из соображений безопасности в языке MQL5 строго контролируется работа с файлами. Файлы, с которыми проводятся файловые операции средствами языка MQL5, не могут находиться за пределами файловой "песочницы". Файл открывается в папке клиентского терминала в подпапке MQL5\Files (или каталог_агента_тестирования\MQL5\Files в случае тестирования). Если среди флагов указан FILE_COMMON, то файл открывается в общей папке всех клиентских терминалов \Terminal\Common\Files.

 


Разработка класса CFileNavigator

Для создания файлового навигатора в библиотеке на текущей стадии разработки уже есть всё необходимое. Элемент «Древовидный список», представленный чуть раньше, по сути, является основой создания файловых навигаторов. Кроме древовидного списка с областью содержания, создадим ещё адресную строку, в которой будет отображаться полный путь относительно выделенного в текущий момент элемента в древовидном списке.

Предоставим возможность выбора, какие директории отображать в корневом каталоге. Например, можно ограничиться только одной из директорий файловой «песочницы» терминала, а можно дать доступ сразу к двум. Для этого нужно добавить в файл Enums.mqh перечисление ENUM_FILE_NAVIGATOR_CONTENT (см. листинг кода ниже):

  • FN_BOTH – показывать обе директории.
  • FN_ONLY_MQL – показывать директорию только локальной папки клиентского терминала.
  • FN_ONLY_COMMON – показывать директорию только общей папки всех установленных терминалов.

//+------------------------------------------------------------------+
//| Перечисление содержания файлового навигатора                     |
//+------------------------------------------------------------------+
enum ENUM_FILE_NAVIGATOR_CONTENT
  {
   FN_BOTH        =0,
   FN_ONLY_MQL    =1,
   FN_ONLY_COMMON =2
  };

Далее создаём файл FileNavigator.mqh с классом CFileNavigator и подключаем его к движку библиотеки (файл WndContainer.mqh):

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

В классе CFileNavigator должен быть реализован стандартный набор для всех элементов библиотеки методов, как это показано в листинге ниже: 

//+------------------------------------------------------------------+
//| Класс для создания файлового навигатора                          |
//+------------------------------------------------------------------+
class CFileNavigator : public CElement
  {
private:
   //--- Указатель на форму, к которой элемент присоединён
   CWindow          *m_wnd;
   //---
public:
                     CFileNavigator(void);
                    ~CFileNavigator(void);
   //--- Сохраняет указатель формы
   void              WindowPointer(CWindow &object)                           { m_wnd=::GetPointer(object);                }
   //---
public:
   //--- Обработчик событий графика
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Таймер
   virtual void      OnEventTimer(void) {}
   //--- Перемещение элемента
   virtual void      Moving(const int x,const int y);
   //--- (1) Показ, (2) скрытие, (3) сброс, (4) удаление
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) Установка, (2) сброс приоритетов на нажатие левой кнопки мыши
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
   //--- Сбросить цвет
   virtual void      ResetColors(void) {}
  };

Перечислим свойства, которые будут доступны пользователю библиотеки для настройки файлового навигатора.

  • Ширина области древовидного списка
  • Ширина области содержания
  • Цвет фона областей
  • Цвет рамок
  • Цвет фона адресной строки
  • Цвет текста в адресной строке
  • Высота адресной строки
  • Картинки для папок и файлов
  • Режим навигатора (Показывать все/Только папки)
  • Режим содержания файлового навигатора (Общая папка/Локальная/Всё)

//+------------------------------------------------------------------+
//| Класс для создания файлового навигатора                          |
//+------------------------------------------------------------------+
class CFileNavigator : public CElement
  {
private:
   //--- Ширина области древовидного списка
   int               m_treeview_area_width;
   //--- Ширина области содержания
   int               m_content_area_width;
   //--- Цвет фона и рамки фона
   color             m_area_color;
   color             m_area_border_color;
   //--- Цвет фона адресной строки
   color             m_address_bar_back_color;
   //--- Цвет текста в адресной строке
   color             m_address_bar_text_color;
   //--- Высота адресной строки
   int               m_address_bar_y_size;
   //--- Картинки для (1) папок и (2) файлов
   string            m_file_icon;
   string            m_folder_icon;
   //--- Режим содержания файлового навигатора
   ENUM_FILE_NAVIGATOR_CONTENT m_navigator_content;
   //--- Приоритеты на нажатие левой кнопки мыши
   int               m_zorder;
   //---
public:
   //--- (1) Режим навигатора (Показывать все/Только папки), (2) содержание навигатора (Общая папка/Локальная/Всё)
   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) Высота адресной строки, (2) ширина древовидного списка и (3) списка содержания
   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) Цвет фона и (2) рамки фона
   void              AreaBackColor(const color clr)                           { m_area_color=clr;                          }
   void              AreaBorderColor(const color clr)                         { m_area_border_color=clr;                   }
   //--- (1) Цвет фона и (2) текста адресной строки
   void              AddressBarBackColor(const color clr)                     { m_address_bar_back_color=clr;              }
   void              AddressBarTextColor(const color clr)                     { m_address_bar_text_color=clr;              }
   //--- Установка пути к файлам для (1) файлов и (2) папок
   void              FileIcon(const string file_path)                         { m_file_icon=file_path;                     }
   void              FolderIcon(const string file_path)                       { m_folder_icon=file_path;                   }
  };

Инициализация полей-свойств значениями по умолчанию будет осуществляться в конструкторе класса (см. листинг кода ниже). Картинки по умолчанию для папок и файлов навигатора будут подключены к библиотеке как ресурсы. Скачать их можно из архива, приложенного в конце статьи.

#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")
  {
//--- Сохраним имя класса элемента в базовом классе
   CElement::ClassName(CLASS_NAME);
//--- Установим приоритеты на нажатие левой кнопки мыши
   m_zorder=0;
  }

Для создания файлового навигатора понадобятся два приватных метода и один публичный. Иерархическая структура файловой системы будет реализована с помощью класса CTreeView, который был представлен в предыдущей статье. Файл с этим классом нужно подключить к файлу FileNavigator.mqh.

//+------------------------------------------------------------------+
//|                                                FileNavigator.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
#include "TreeView.mqh"
//+------------------------------------------------------------------+
//| Класс для создания файлового навигатора                          |
//+------------------------------------------------------------------+
class CFileNavigator : public CElement
  {
private:
   //--- Объекты для создания элемента
   CRectCanvas       m_address_bar;
   CTreeView         m_treeview;
   //---
public:
   //--- Методы для создания файлового навигатора
   bool              CreateFileNavigator(const long chart_id,const int subwin,const int x,const int y);
   //---
private:
   bool              CreateAddressBar(void);
   bool              CreateTreeView(void);
  };

Перед тем, как описать некоторые нюансы методов для создания файлового навигатора, рассмотрим, как перед созданием элемента будет формироваться иерархическая структура файловой системы терминала. 

 


Формирование иерархической структуры файловой системы

Перед созданием файлового навигатора нужно сначала просканировать файловую систему терминала и сохранить параметры всех её элементов для создания древовидного списка. Все эти параметры были подробно рассмотрены в предыдущей статье, поэтому здесь приведём лишь список массивов для тех параметров, которые нужно сохранить для формирования древовидного списка.

  • Общий индекс
  • Общий индекс предыдущего узла
  • Имя папки/файла
  • Локальный индекс
  • Уровень узла
  • Локальный индекс предыдущего узла
  • Общее количество элементов в папке
  • Количество папок в папке
  • Признак папки
  • Состояние пункта (свёрнут/открыт)

Для подготовки параметров нам понадобятся два списка массивов — основной (префикс g) и вспомогательный (префикс l):

class CFileNavigator : public CElement
  {
private:
   //--- Основные массивы для хранения данных
   int               m_g_list_index[];           // общий индекс
   int               m_g_prev_node_list_index[]; // общий индекс предыдущего узла
   string            m_g_item_text[];            // имя папки/файла
   int               m_g_item_index[];           // локальный индекс
   int               m_g_node_level[];           // уровень узла
   int               m_g_prev_node_item_index[]; // локальный индекс предыдущего узла
   int               m_g_items_total[];          // всего элементов в папке
   int               m_g_folders_total[];        // количество папок в папке
   bool              m_g_is_folder[];            // признак папки
   bool              m_g_item_state[];           // состояние пункта (свёрнут/открыт)
   //--- Вспомогательные массивы для сбора данных
   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[];
  };

Чтобы просканировать файловую систему терминала, собрать все данные и сохранить их в массивы, понадобятся несколько вспомогательных методов. Для работы со вспомогательным массивами нужен метод CFileNavigator::AuxiliaryArraysResize(), с помощью которого можно будет изменять их размер относительно используемого в работе в текущий момент уровня узла (см. код ниже). Иными словами, размер массива будет устанавливаться на один элемент больше, чем текущее значение уровня узла, которое нужно передать в метод в качестве единственного аргумента. Если текущее значение уровня узла 0, то размер массивов будет устанавливаться равным 1, если уровень узла 1, то размер массивов тогда будет 2, и т.д. Поскольку уровень узла в процессе сбора данных будет меняться и в большую, и в меньшую сторону, то соответствующим образом будет регулироваться и размер массивов. В этом же методе будет осуществляться инициализация элемента массива текущего узла. 

class CFileNavigator : public CElement
  {
private:
   //--- Увеличивает размер массивов на один элемент
   void              AuxiliaryArraysResize(const int node_level);
  };
//+------------------------------------------------------------------+
//| Увеличивает размер вспомогательных массивов на один элемент      |
//+------------------------------------------------------------------+
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);
//--- Инициализация последнего значения
   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;
  }

Для добавления элемента с параметрами в основные массивы будет использоваться метод CFileNavigator::AddItem(). Здесь массивы при каждом вызове метода будут увеличиваться на один элемент, в последнюю ячейку которого будут сохраняться значения переданных аргументов. 

class CFileNavigator : public CElement
  {
private:
   //--- Добавляет пункт в массивы
   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);
  };
//+------------------------------------------------------------------+
//| Добавляет пункт с указанными параметрами в массивы               |
//+------------------------------------------------------------------+
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)
  {
//--- Увеличим размер массивов на один элемент
   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);
//--- Сохраним значения переданных параметров
   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;
  }

При сканировании файловой системы, каждый раз при нахождении папки нужно переходить в неё для просмотра её содержания. Для такой проверки будет использоваться метод CFileNavigator::IsFolder(), который подскажет, является ли текущий элемент папкой или файлом. В качестве единственного параметра в него нужно передать имя элемента файловой системы. Если в имени элемента обнаружится символ наклонной черты «\», то это означает, что это папка, и метод вернёт true. Если же это файл, то метод вернёт false

Есть еще один способ проверить, является ли элемент файлом — с помощью системной функции FileIsExist(). Эта функция возвращает true, если переданное имя элемента в полученном в последний раз хэндле поиска окажется файлом. Если же это директория, то функция сгенерирует ошибку ERR_FILE_IS_DIRECTORY.  

class CFileNavigator : public CElement
  {
private:
   //--- Определяет, передано имя папки или файла
   bool              IsFolder(const string file_name);
  };
//+------------------------------------------------------------------+
//| Определяет, передано имя папки или файла                         |
//+------------------------------------------------------------------+
bool CFileNavigator::IsFolder(const string file_name)
  {
//--- Если в имени есть символы "\\", то это папка
   if(::StringFind(file_name,"\\",0)>-1)
      return(true);
//---
   return(false);
  }

Для формирования древовидного списка нужно указывать количество элементов в пункте, а также количество элементов, являющихся папками. Поэтому понадобятся соответствующие методы, которые помогут определить значения для этих параметров. Такими методами здесь будут выступать CFileNavigator::ItemsTotal() и CFileNavigator::FoldersTotal(). Единственное отличие между ними — в том, что во втором счётчик увеличивается, только если проверяемый элемент является папкой. В оба метода передаются два аргумента: (1) путь и (2) область поиска. Под областью поиска здесь подразумевается общая папка всех терминалов или локальная папка терминала. Далее с помощью системной функции FileFindFirst() осуществляется попытка получить хэндл поиска по указанному пути и одновременно имя первого элемента, если он будет найден. Признаком того, что первый элемент найден, будет валидный хэндл и имя объекта

Далее в цикле, с помощью системной функции FileFindNext(), поочерёдно осуществляется попытка получить доступ ко всем остальным элементам в этой же директории. Ключ к этой директории — полученный ранее хэндл. Если элемент найден, то функция возвращает true и счётчик увеличивается. Поиск прекращается, как только функция вернёт false. В конце обоих методов хэндл поиска нужно закрыть. Для этого используется системная функция FileFindClose()

class CFileNavigator : public CElement
  {
private:
   //--- Возвращает количество (1) элементов и (2) папок в указанной директории
   int               ItemsTotal(const string search_path,const int mode);
   int               FoldersTotal(const string search_path,const int mode);
  };
//+------------------------------------------------------------------+
//| Считает количество файлов в текущей директории                   |
//+------------------------------------------------------------------+
int CFileNavigator::ItemsTotal(const string search_path,const int search_area)
  {
   int    counter       =0;              // счётчик элементов 
   string file_name     ="";             // имя файла
   long   search_handle =INVALID_HANDLE; // хэндл поиска
//--- Получаем первый файл в текущей директории
   search_handle=::FileFindFirst(search_path,file_name,search_area);
//--- Если директория не пуста
   if(search_handle!=INVALID_HANDLE && file_name!="")
     {
      //--- Посчитаем количество объектов в текущей директории
      counter++;
      while(::FileFindNext(search_handle,file_name))
         counter++;
     }
//--- Закрываем хэндл поиска
   ::FileFindClose(search_handle);
   return(counter);
  }
//+------------------------------------------------------------------+
//| Считает количество папок в текущей директории                    |
//+------------------------------------------------------------------+
int CFileNavigator::FoldersTotal(const string search_path,const int search_area)
  {
   int    counter       =0;              // счётчик элементов 
   string file_name     ="";             // имя файла
   long   search_handle =INVALID_HANDLE; // хэндл поиска
//--- Получаем первый файл в текущей директории
   search_handle=::FileFindFirst(search_path,file_name,search_area);
//--- Если не путо, то в цикле считаем кол-во объектов в текущей директории
   if(search_handle!=INVALID_HANDLE && file_name!="")
     {
      //--- Если это папка, увеличим счётчик
      if(IsFolder(file_name))
         counter++;
      //--- Пройдёмся далее по списку и посчитаем другие папки
      while(::FileFindNext(search_handle,file_name))
        {
         if(IsFolder(file_name))
            counter++;
        }
     }
//--- Закрываем хэндл поиска
   ::FileFindClose(search_handle);
   return(counter);
  }

Во время сбора параметров элементов файловой системы нужно будет получать локальные индексы предыдущих узлов. Для этих целей будет использоваться метод CFileNavigator::PrevNodeItemIndex(). В качестве аргументов в него нужно передать (1) текущий индекс пункта в корневом каталоге и (2) текущий уровень узла. Значения обоих аргументов контролируются счётчиками во внешних циклах главных методов, внутри которых будет вызываться этот метод. 

class CFileNavigator : public CElement
  {
private:
   //--- Возвращает локальный индекс предыдущего узла относительно переданных параметров
   int               PrevNodeItemIndex(const int root_index,const int node_level);
  };
//+------------------------------------------------------------------+
//| Возвращает локальный индекс предыдущего узла                     |
//| относительно переданных параметров                               |
//+------------------------------------------------------------------+
int CFileNavigator::PrevNodeItemIndex(const int root_index,const int node_level)
  {
   int prev_node_item_index=0;
//--- Если не в корневом каталоге
   if(node_level>1)
      prev_node_item_index=m_l_item_index[node_level-1];
   else
     {
      //--- Если не первый элемент списка
      if(root_index>0)
         prev_node_item_index=m_l_item_index[node_level-1];
     }
//--- Вернём локальный индекс предыдущего узла
   return(prev_node_item_index);
  }

Каждый раз, когда найдена папка, будет осуществляться переход в неё, то есть, на следующий уровень узла. Для этого вызывается метод CFileNavigator::ToNextNode(). Некоторые аргументы-параметры передаются по ссылке, чтобы обеспечить возможность управления их значениями

Здесь в самом начале метода формируется путь для подсчёта элементов в этой директории. Затем во вспомогательные массивы по индексу текущего уровня узла сохраняются параметры элемента. После этого нужно получить индекс пункта предыдущего узла и добавить пункт с указанными параметрами в общие массивы. То есть, только здесь папка, в которую мы зашли, добавляется в массивы с подготовленными для неё параметрами.

Затем счётчик узлов увеличивается, и относительно него корректируется размер вспомогательных массивов. Далее, уже для нового уровня узла, нужно сохранить некоторые параметры: (1) путь, (2) количество элементов и (3) количество папок. В конце метода (1) счётчик локальных индексов обнуляется и (2) закрывается текущий хэндл поиска.  

class CFileNavigator : public CElement
  {
private:
   //--- Переход на следующий узел
   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);
  };
//+------------------------------------------------------------------+
//| Переход на следующий узел                                        |
//+------------------------------------------------------------------+
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)
  {
//--- Фильтр поиска (* - проверить все файлы/папки)
   string filter="*";
//--- Сформируем путь
   string search_path=m_l_path[node_level]+item_text+filter;
//--- Получим и сохраним данные
   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;
//--- Получим индекс пункта предыдущего узла
   int prev_node_item_index=PrevNodeItemIndex(root_index,node_level);
//--- Добавим пункт с указанными данными в общие массивы
   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);
//--- Увеличим счётчик узлов
   node_level++;
//--- Увеличим размер массивов на один элемент
   AuxiliaryArraysResize(node_level);
//--- Получим и сохраним данные
   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);
//--- Обнулить счётчик локальных индексов
   item_index=0;
//--- Закрываем хэндл поиска
   ::FileFindClose(handle);
  }

Теперь рассмотрим главный цикл, в котором осуществляются основные действия. Этот цикл вынесен для удобства в отдельный метод CFileNavigator::FileSystemScan(). В методе осуществляется чтение файловой системы терминала с записью параметров найденных элементов в основные массивы. Эти массивы позже будут использоваться для построения древовидного списка. Цикл будет работать до тех пор, пока алгоритм не дойдёт до конца списка или программа не будет удалена с графика. 

Алгоритм работает следующим образом. В самом начале цикла происходит проверка на начало списка (первый элемент списка) текущей директории. Если проверяемый элемент действительно первый, то мы получаем хэндл и имя первого найденного элемента в указанной области файловой системы и сохраняем во вспомогательных массивах, в индексе текущего уровня узла, количество элементов и папок. 

Если это не первый индекс, то проверяется последовательность локальных индексов в текущем узле. Если индекс этого узла уже был, то счётчик локальных индексов увеличивается и осуществляется переход к следующему элементу в текущей директории.

Если алгоритм дошёл до следующего блока кода, то нужно проверить, не выходим мы ли за пределы списка элементов, относящегося к корневому узлу. Если это так, то цикл останавливается, что также означает закрытие хэндла поиска (уже вне блока цикла) и выход программы из метода. Если же мы дошли до конца списка любого другого узла, кроме корневого, — значит, нужно перейти на уровень выше. Здесь (1) счётчик узлов уменьшается на один уровень назад, (2) обнуляется счётчик локальных индексов, (3) закрывается хэндл поиска и (4) осуществляется переход к следующей итерации цикла.

Если же ни одно условие в предыдущей конструкции if-else не исполнилось, то сначала проверяется, является ли текущий элемент файловой системы папкой. Если да, то с помощью ранее рассмотренного метода CFileNavigator::ToNextNode() осуществляется переход на следующий уровень. Затем увеличивается счётчик общих индексов и даётся команда на переход к следующей итерации. 

В случае, если элемент файловой системы оказался файлом, то сначала мы получаем локальный индекс предыдущего узла. Затем добавляем пункт с указанными параметрами в общие массивы. Увеличиваем счётчик общих индексов, а также счётчик локальных индексов. И, в качестве завершающей операции цикла, осуществляется переход к следующему элементу файловой системы терминала. После этого начинается очередная итерация цикла, и алгоритм проходит через все условия, описанные выше. 

class CFileNavigator : public CElement
  {
private:
   //--- Читает файловую систему и записывает параметры в массивы
   void              FileSystemScan(const int root_index,int &list_index,int &node_level,int &item_index,int search_area);
  };
//+------------------------------------------------------------------+
//| Читает файловую систему терминала и записывает                   |
//| параметры элементов в массивы                                    |
//+------------------------------------------------------------------+
void CFileNavigator::FileSystemScan(const int root_index,int &list_index,int &node_level,int &item_index,int search_area)
  {
   long   search_handle =INVALID_HANDLE; // Хэндл поиска папки/файла
   string file_name     ="";             // Имя найденного элемента (файла/папки)
   string filter        ="*";            // Фильтр поиска (* - проверить все файлы/папки)
//--- Сканируем директории и заносим данные в массивы
   while(!::IsStopped())
     {
      //--- Если это начало списка директории
      if(item_index==0)
        {
         //--- Путь для поиска всех элементов
         string search_path=m_l_path[node_level]+filter;
         //--- Получаем хэндл и имя первого файла
         search_handle=::FileFindFirst(search_path,file_name,search_area);
         //--- Получим кол-во файлов и папок в указанной директории
         m_l_item_total[node_level]    =ItemsTotal(search_path,search_area);
         m_l_folders_total[node_level] =FoldersTotal(search_path,search_area);
        }
      //--- Если индекс этого узла уже был, перейдём к следующему файлу
      if(m_l_item_index[node_level]>-1 && item_index<=m_l_item_index[node_level])
        {
         //--- Увеличим счётчик локальных индексов
         item_index++;
         //--- Переходим к следующему элементу
         ::FileFindNext(search_handle,file_name);
         continue;
        }
      //--- Если дошли до конца списка в корневом узле, закончим цикл
      if(node_level==1 && item_index>=m_l_item_total[node_level])
         break;
      //--- Если дошли до конца списка в любом узле, кроме корневого
      else if(item_index>=m_l_item_total[node_level])
        {
         //--- Перевести счётчик узлов на один уровень назад
         node_level--;
         //--- Обнулить счётчик локальных индексов
         item_index=0;
         //--- Закрываем хэндл поиска
         ::FileFindClose(search_handle);
         continue;
        }
      //--- Если это папка
      if(IsFolder(file_name))
        {
         //--- Перейдём на следующий узел
         ToNextNode(root_index,list_index,node_level,item_index,search_handle,file_name,search_area);
         //--- Увеличить счётчик общих индексов и начать новую итерацию
         list_index++;
         continue;
        }
      //--- Получим локальный индекс предыдущего узла
      int prev_node_item_index=PrevNodeItemIndex(root_index,node_level);
      //--- Добавим пункт с указанными данными в общие массивы
      AddItem(list_index,file_name,node_level,prev_node_item_index,item_index,0,0,false);
      //--- Увеличим счётчик общих индексов
      list_index++;
      //--- Увеличим счётчик локальных индексов
      item_index++;
      //--- Переходим к следующему элементу
      ::FileFindNext(search_handle,file_name);
     }
//--- Закрываем хэндл поиска
   ::FileFindClose(search_handle);
  }

Теперь рассмотрим главный метод CFileNavigator::FillArraysData(), в котором вызываются все описанные выше методы.

Прежде всего, здесь задаётся последовательность директорий общей и локальной папок терминала. Эта последовательность зависит от того, какой режим (ENUM_FILE_NAVIGATOR_CONTENT) установлен в свойствах файлового навигатора. По умолчанию последовательность установлена так, чтобы первой в списке была общая папка терминала, а второй – локальная папка терминала. Это работает только в том случае, если установлен режим FN_BOTH ("отображать содержимое обеих директорий"). Если выбран режим "показывать содержимое одной директории", то начало (begin) и конец (end) диапазона цикла будут инициализированы соответствующим образом.

После того, как область поиска в начале тела цикла определена, последовательно осуществляются нижеперечисленные действия.

  • Обнуляется счётчик локальных индексов
  • Размер вспомогательных массивов изменяется относительно текущего уровня узла
  • В первый индекс этих же массивов сохраняется количество элементов и папок в текущей директории
  • В главные массивы добавляется элемент с указанными параметрами. Так как здесь это корневой каталог, то название текущего элемента может быть для одной из двух директорий: "Common\\Files\\" или "MQL5\\Files\\"
  • Увеличиваются счётчики общих индексов и уровней узлов
  • Снова относительно текущего значения уровня узла изменяется размер вспомогательных массивов
  • Если сейчас область поиска находится в локальной папке терминала, то корректируются значения для первых индексов вспомогательных массивов: (1) локальных индексов и (2) общих индексов предыдущего узла.

И, наконец, вызывается метод CFileNavigator::FileSystemScan() для чтения файловой системы в указанной области поиска и сохранения параметров её элементов в основные массивы, предназначенные для формирования древовидного списка.  

class CFileNavigator : public CElement
  {
private:
   //--- Заполняет массивы параметрами элементов файловой системы терминала
   void              FillArraysData(void);
  };
//+------------------------------------------------------------------+
//| Заполняет массивы параметрами элементов файловой системы         |
//+------------------------------------------------------------------+
void CFileNavigator::FillArraysData(void)
  {
//--- Счётчики (1) общих индексов, (2) уровней узлов, (3) локальных индексов
   int list_index =0;
   int node_level =0;
   int item_index =0;
//--- Если нужно отображать обе директории (Общая (0)/Локальная (1))
   int begin=0,end=1;
//--- Если нужно отображать содержимое только локальной директории
   if(m_navigator_content==FN_ONLY_MQL)
      begin=1;
//--- Если нужно отображать содержимое только общей директории
   else if(m_navigator_content==FN_ONLY_COMMON)
      begin=end=0;
//--- Пройдёмся по указанным директориям
   for(int root_index=begin; root_index<=end; root_index++)
     {
      //--- Определим директорию для сканирования файловой структуры
      int search_area=(root_index>0)? 0 : FILE_COMMON;
      //--- Обнулим счётчик локальных индексов
      item_index=0;
      //--- Увеличим размер вспомогательных массивов на один элемент (относительно уровня узла)
      AuxiliaryArraysResize(node_level);
      //--- Получим кол-во файлов и папок в указанной директории (* - проверить все файлы/папки)
      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);
      //--- Добавим пункт с названием корневого каталога
      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);
      //--- Увеличим счётчики общих индексов и уровней узлов
      list_index++;
      node_level++;
      //--- Увеличим размер массивов на один элемент (относительно уровня узла)
      AuxiliaryArraysResize(node_level);
      //--- Инициализация первых элементов для директории локальной папки терминала
      if(root_index>0)
        {
         m_l_item_index[0]           =root_index;
         m_l_prev_node_list_index[0] =list_index-1;
        }
      //--- Сканируем директории и заносим данные в массивы
      FileSystemScan(root_index,list_index,node_level,item_index,search_area);
     }
  }

 


Методы для создания элемента

Вызов метода CFileNavigator::FillArraysData() осуществляется только один раз, перед созданием элемента «Файловый навигатор». По сути, пользователь библиотеки может даже и не знать о нём и о том, как он устроен. Достаточно будет просто указать некоторые общие свойства, которые влияют на внешний вид и содержание файлового навигатора.

//+------------------------------------------------------------------+
//| Создаёт файловый навигатор                                       |
//+------------------------------------------------------------------+
bool CFileNavigator::CreateFileNavigator(const long chart_id,const int subwin,const int x,const int y)
  {
//--- Выйти, если нет указателя на форму
   if(::CheckPointer(m_wnd)==POINTER_INVALID)
     {
      ::Print(__FUNCTION__," > Перед созданием файлового навигатора ему нужно передать "
              "указатель на форму: CFileNavigator::WindowPointer(CWindow &object).");
      return(false);
     }
//--- Сканируем файловую систему терминала и заносим данные в массивы
   FillArraysData();
//--- Инициализация переменных
   m_id       =m_wnd.LastId()+1;
   m_chart_id =chart_id;
   m_subwin   =subwin;
   m_x        =x;
   m_y        =y;
//--- Отступы от крайней точки
   CElement::XGap(CElement::X()-m_wnd.X());
   CElement::YGap(CElement::Y()-m_wnd.Y());
//--- Создание элемента
   if(!CreateAddressBar())
      return(false);
   if(!CreateTreeView())
      return(false);
//--- Скрыть элемент, если окно диалоговое или оно минимизировано
   if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized())
      Hide();
//---
   return(true);
  }

Создание адресной строки — первое действие при создании файлового навигатора. Это будет один объект типа OBJ_BITMAP_LABEL, содержимое которого будет полностью нарисовано. Ранее уже были показаны примеры создания элементов, которые рисуются на холсте, поэтому исключим повторения и коснёмся только тех моментов, которые относятся к разрабатываемому элементу. 

Для отрисовки адресной строки понадобятся два метода:

  • Метод CFileNavigator::Border() – используется для рисования рамки адресной строки.
  • Метод CFileNavigator::UpdateAddressBar() –  главный метод для отрисовки и отображения последних изменений, к которым относится выбранная в древовидном списке директория.

Приведём здесь код только метода CFileNavigator::UpdateAddressBar(), так как рисование рамки уже рассматривалось на примере других элементов. Например, в статье Графические интерфейсы IV - Информационные элементы интерфейса (Глава 1)

Ранее уже упоминалось, что у пользователя есть возможность перед созданием файлового навигатора указать цвет фона адресной строки и её размер по оси Y. Текст в области холста для рисования будет позиционироваться с отступом 5 пикселей по оси X от левого края, а позиционирование по оси Y должно быть точно по центру. Имея значение размера по оси Y, чтобы получить Y-координату, нужно просто разделить высоту адресной строки на 2. Важно также при вызове метода TextOut() для отрисовки текста на холсте передать в него флаги для способа привязки слева и по центру

Во время первой установки файлового навигатора путь ещё не инициализирован, и поле класса m_current_path, предназначенное для его хранения, содержит пустую строку. Так как элементов файловой системы может быть очень много, то формирование массивов и создание  древовидного списка может занять какое-то время. Так как адресная строка создаётся первой, то в неё можно вывести текст, предупреждающий пользователя о том, что нужно немного подождать. Например, здесь это будет текст "Loading. Please wait...". 

В конце метода холст обновляется для отображения последних внесённых изменений. 

class CFileNavigator : public CElement
  {
private:
   //--- Рисует рамку для адресной строки
   void              Border(void);
   //--- Отображает текущий путь в адресной строке
   void              UpdateAddressBar(void);
  };
//+------------------------------------------------------------------+
//| Отображает текущий путь в адресной строке                        |
//+------------------------------------------------------------------+
void CFileNavigator::UpdateAddressBar(void)
  {
//--- Координаты
   int x=5;
   int y=m_address_bar_y_size/2;
//--- Очистить фон
   m_address_bar.Erase(::ColorToARGB(m_address_bar_back_color,0));
//--- Нарисовать рамку фона
   Border();
//--- Свойства текста
   m_address_bar.FontSet("Calibri",14,FW_NORMAL);
//--- Если путь ещё не установлен, показать строку по умолчанию
   if(m_current_full_path=="")
      m_current_full_path="Loading. Please wait...";
//--- Выведем путь в адресную строку файлового навигатора
   m_address_bar.TextOut(x,y,m_current_path,::ColorToARGB(m_address_bar_text_color),TA_LEFT|TA_VCENTER);
//--- Обновим холст для рисования
   m_address_bar.Update();
  }

Ширина адресной строки рассчитывается перед её созданием в методе CFileNavigator::CreateAddressBar(). Если в настройках установлено, что область содержания отключена, то ширина адресной строки будет равна ширине области древовидного списка. В других случаях она рассчитывается по такому же принципу, как это было реализовано в классе древовидного списка (CTreeView) для общей ширины элемента.

После того, как объект создан, вызывается метод CFileNavigator::UpdateAddressBar() для отрисовки фона, рамки и вывода сообщения по умолчанию. 

//+------------------------------------------------------------------+
//| Создаёт адресную строку                                          |
//+------------------------------------------------------------------+
bool CFileNavigator::CreateAddressBar(void)
  {
//--- Формирование имени объекта
   string name=CElement::ProgramName()+"_file_navigator_address_bar_"+(string)CElement::Id();
//--- Координаты
   int x =CElement::X();
   int y =CElement::Y();
//--- Размеры:
//    Рассчитаем ширину
   int x_size=0;
//--- Если области содержания не будет
   if(m_content_area_width<0)
      x_size=m_treeview_area_width;
   else
     {
      //--- Если указана конкретная ширина области содержания
      if(m_content_area_width>0)
         x_size=m_treeview_area_width+m_content_area_width-1;
      //--- Если правый край области содержания должен быть у правого края формы
      else
         x_size=m_wnd.X2()-x-2;
     }
//--- Высота
   int y_size=m_address_bar_y_size;
//--- Создание объекта
   if(!m_address_bar.CreateBitmapLabel(m_chart_id,m_subwin,name,x,y,x_size,y_size,COLOR_FORMAT_XRGB_NOALPHA))
      return(false);
//--- Прикрепить к графику
   if(!m_address_bar.Attach(m_chart_id,name,m_subwin,1))
      return(false);
//--- Установим свойства
   m_address_bar.Background(false);
   m_address_bar.Z_Order(m_zorder);
   m_address_bar.Tooltip("\n");
//--- Сохраним размеры
   CElement::X(x);
   CElement::Y(y);
//--- Сохраним размеры
   CElement::XSize(x_size);
   CElement::YSize(y_size);
//--- Отступы от крайней точки
   m_address_bar.XGap(x-m_wnd.X());
   m_address_bar.YGap(y-m_wnd.Y());
//--- Обновить адресную строку
   UpdateAddressBar();
//--- Сохраним указатель объекта
   CElement::AddToArray(m_address_bar);
//--- Скрыть элемент, если окно диалоговое или оно минимизировано
   if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized())
      m_address_bar.Timeframes(OBJ_NO_PERIODS);
//---
   return(true);
  }

Мы подошли к моменту, когда при создании файлового навигатора вызывается метод CFileNavigator::CreateTreeView() для установки древовидного списка. Из предыдущей статьи вы должны помнить, что перед созданием элемента типа CTreeView сначала нужно в его массивы добавить пункты с указанием их параметров. На этом этапе все параметры для пунктов помещены в основные массивы класса CFileNavigator. Осталось передать их в цикле в класс древовидного списка

В этом же цикле будет определяться картинка для каждого пункта. Кроме этого, в названиях тех элементов, которые являются папками, нужно удалить последний символ (‘\’)

//+------------------------------------------------------------------+
//| Создаёт древовидный список                                       |
//+------------------------------------------------------------------+
bool CFileNavigator::CreateTreeView(void)
  {
//--- Сохраним указатель на окно
   m_treeview.WindowPointer(m_wnd);
//--- Установим свойства
   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);
//--- Формируем массивы древовидного списка
   int items_total=::ArraySize(m_g_item_text);
   for(int i=0; i<items_total; i++)
     {
      //--- Определим картинку для пункта (папка/файл)
      string icon_path=(m_g_is_folder[i])? m_folder_icon : m_file_icon;
      //--- Если это папка, удалим последний символ ('\') в строке 
      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);
      //--- Добавим пункт в древовидный список
      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]);
     }
//--- Создать древовидный список
   if(!m_treeview.CreateTreeView(m_chart_id,m_subwin,m_x,m_y+m_address_bar_y_size))
      return(false);
//---
   return(true);
  }

 

 


Обработчик событий

В полях класса будут сохраняться (1) полный путь выделенной в древовидном списке директории относительно файловой системы, включая метку тома жёсткого диска, а также (2) путь относительно песочницы терминала и (3) область текущей директории. Для получения этих значений нужно использовать соответствующие методы. Кроме этого, нам понадобится метод CFileNavigator::SelectedFile() для получения выделенного элемента, который является файлом.

class CFileNavigator : public CElement
  {
private:
   //--- Текущий путь относительно файловой "песочницы" терминала
   string            m_current_path;
   //--- Текущий полный путь относительно файловой системы включая метку тома жёсткого диска
   string            m_current_full_path;
   //--- Область текущей директории
   int               m_directory_area;
   //---
public:
   //--- Возвращает (1) текущий путь и (2) полный путь, (3) выделенный файл
   string            CurrentPath(void)                                  const { return(m_current_path);                    }
   string            CurrentFullPath(void)                              const { return(m_current_full_path);               }
   //--- Возвращает (1) область директории и (2) выделенный файл
   int               DirectoryArea(void)                                const { return(m_directory_area);                  }
   string            SelectedFile(void)                                 const { return(m_treeview.SelectedItemFileName()); }
  };

Обработчик событий файлового навигатора будет настроен на приём только одного события с идентификатором ON_CHANGE_TREE_PATH. Его генерирует древовидный список при выделении того или иного пункта в его структуре. Для обработки сообщения с этим идентификатором реализован метод CFileNavigator::OnChangeTreePath(). 

Здесь сначала получаем путь, сохранённый в древовидном списке. Затем, в зависимости от того, к какой категории (общей или локальной) относится этот путь, (1) получаем адрес корневого каталога данных и (2) формируем краткий и полный путь и сохраняем флаг области директории

class CFileNavigator : public CElement
  {
private:
   //--- Обработка события выбора нового пути в древовидном списке
   void              OnChangeTreePath(void);
  };
//+------------------------------------------------------------------+
//| Обработка события выбора нового пути в древовидном списке        |
//+------------------------------------------------------------------+
void CFileNavigator::OnChangeTreePath(void)
  {
//--- Получим текущий путь
   string path=m_treeview.CurrentFullPath();
//--- Если это общая папка терминалов
   if(::StringFind(path,"Common\\Files\\",0)>-1)
     {
      //--- Получим адрес общей папки терминалов
      string common_path=::TerminalInfoString(TERMINAL_COMMONDATA_PATH);
      //--- Удалим в строке (принятой в событии) префикс "Common\"
      path=::StringSubstr(path,7,::StringLen(common_path)-7);
      //--- Сформируем путь (краткую и полную версию)
      m_current_path      =::StringSubstr(path,6,::StringLen(path)-6);
      m_current_full_path =common_path+"\\"+path;
      //--- Сохраним область директории
      m_directory_area=FILE_COMMON;
     }
//--- Если это локальная папка терминала
   else if(::StringFind(path,"MQL5\\Files\\",0)>-1)
     {
      //--- Получим адрес данных в локальной папке терминала
      string local_path=::TerminalInfoString(TERMINAL_DATA_PATH);
      //--- Сформируем путь (краткую и полную версию)
      m_current_path      =::StringSubstr(path,11,::StringLen(path)-11);
      m_current_full_path =local_path+"\\"+path;
      //--- Сохраним область директории
      m_directory_area=0;
     }
//--- Отобразим текущий путь в адресной строке
   UpdateAddressBar();
  }

 

В итоге, в обработчике событий элемента, по приходу события с идентификатором ON_CHANGE_TREE_PATH, нужно просто вызвать метод CFileNavigator::OnChangeTreePath(), как это показано в листинге кода ниже:

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CFileNavigator::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Обработка события "Изменение пути в древовидном списке"
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE_TREE_PATH)
     {
      OnChangeTreePath();
      return;
     }
  }

Событие с этим же идентификатором можно будет принимать и в обработчике пользовательского класса. Пример рассмотрен чуть ниже.

 


Интеграция элемента в движок библиотеки

Для правильной работы элемента нужно интегрировать его в движок библиотеки. Дополнения в основном нужно вносить в базовый класс CWndContainer, который содержится в файле WndContainer.mqh, куда подключаются файлы всех остальных элементов библиотеки. Необходимо добавить: 

  • персональный массив для файлового навигатора;
  • метод для получения количества приложения файловых навигаторов этого типа в графическом интерфейсе (CFileNavigator);
  • метод для сохранения указателей на элементы файлового навигатора в базу.

Краткая версия класса CWndContainer (только с тем, что нужно добавить) представлена в листинге кода ниже:

#include "FileNavigator.mqh"
//+------------------------------------------------------------------+
//| Класс для хранения всех объектов интерфейса                      |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- Массив окон
   CWindow          *m_windows[];
   //--- Структура массивов элементов
   struct WindowElements
     {
      //--- Файловые навигаторы
      CFileNavigator   *m_file_navigators[];
     };
   //--- Массив массивов элементов для каждого окна
   WindowElements    m_wnd[];
   //---
public:
   //--- Количество файловых навигаторов
   int               FileNavigatorsTotal(const int window_index);
   //---
private:
   //--- Сохраняет указатели на элементы древовидного списка в базу
   bool              AddFileNavigatorElements(const int window_index,CElement &object);
  };

Подробнее изучить код этих методов Вы можете изучить в приложенных файлах в конце этой статьи. 



Тест файлового навигатора

Итак, у нас всё готово для того, чтобы протестировать файловый навигатор. Для теста возьмём эксперта из предыдущей статьи, сделаем его копию и удалим из пользовательского класса (CProgram) все элементы, кроме главного меню и статусной строки. Для того, чтобы быстро протестировать основные режимы содержания файлового редактора, сделаем в эксперте два внешних параметра, как показано в листинге кода ниже. Эти типы перечислений и режимы были подробно рассмотрены выше в этой статье.

//+------------------------------------------------------------------+
//|                                                      Program.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//--- Внешние параметры
input ENUM_FILE_NAVIGATOR_CONTENT NavigatorContent =FN_BOTH;         // Navigator content
input ENUM_FILE_NAVIGATOR_MODE    NavigatorMode    =FN_ONLY_FOLDERS; // Navigator mode

Теперь нужно объявить экземпляр класса файлового навигатора CFileNavigator, а также метод для создания элемента и отступы от крайней точки формы, к которой он будет присоединён. 

class CProgram : public CWndEvents
  {
private:
   //--- Файловый навигатор
   CFileNavigator    m_navigator;
   //---
private:
   //--- Файловый навигатор
#define NAVIGATOR1_GAP_X      (2)
#define NAVIGATOR1_GAP_Y      (43)
   bool              CreateFileNavigator(void);
  };

Код метода CProgram::CreateFileNavigator() для создания файлового навигатора представлен в листинге кода ниже. Высоту для навигатора установим в размере 10 пунктов, размер которых задан по умолчанию (20 пикселей) в конструкторе класса CFileNavigator. Как уже было отмечено выше, режимы содержания будут управляться внешними параметрами. Внешний вид составных частей можно настроить методами, которые можно получить по указателю. В коде ниже это показано на примере получения указателей древовидного списка и его полос прокрутки. Вызов метода нужно разместить в главном методе создания графического интерфейса

//+------------------------------------------------------------------+
//| Создаёт экспертную панель                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateExpertPanel(void)
  {
//--- Создание формы 1 для элементов управления
//--- Создание элементов управления:
//    Главное меню
//--- Контекстные меню
//--- Создание статусной строки
//--- Создание файлового навигатора
   if(!CreateFileNavigator())
      return(false);
//--- Перерисовка графика
   m_chart.Redraw();
   return(true);
  }
//+------------------------------------------------------------------+
//| Создаёт файловый навигатор                                       |
//+------------------------------------------------------------------+
bool CProgram::CreateFileNavigator(void)
  {
//--- Сохраним указатель на форму
   m_navigator.WindowPointer(m_window1);
//--- Координаты
   int x=m_window1.X()+NAVIGATOR1_GAP_X;
   int y=m_window1.Y()+NAVIGATOR1_GAP_Y;
//--- Установим свойства перед созданием
   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);
//--- Свойства полос прокрутки
   m_navigator.TreeViewPointer().GetScrollVPointer().AreaBorderColor(clrLightGray);
   m_navigator.TreeViewPointer().GetContentScrollVPointer().AreaBorderColor(clrLightGray);
//--- Создание элемента
   if(!m_navigator.CreateFileNavigator(m_chart_id,m_subwin,x,y))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,m_navigator);
   return(true);
  }

В обработчике событий для примера сделаем так, чтобы в журнал выводился полный и краткий путь, а также название выделенного файла в текущий момент. Если файл выделен, то мы будем его открывать и читать первые три строки с выводом в журнал. Обратите внимание, что для контроля области директории флаг, соответствующий местонахождению файла, мы получаем с помощью метода CFileNavigator::DirectoryArea().  

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Обработка события "Изменение пути в древовидном списке"
   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());
      //--- Если выделен файл, прочитаем его (первые три строки)
      if(m_navigator.SelectedFile()!="")
        {
         //--- Сформируем путь к файлу
         string path=m_navigator.CurrentPath()+m_navigator.SelectedFile();
         //--- Получим хэндл указанного файла
         int filehandle=::FileOpen(path,FILE_READ|FILE_TXT|FILE_ANSI|m_navigator.DirectoryArea(),'\n');
         //--- Если хэндл получен, прочитаем три первые строки
         if(filehandle!=INVALID_HANDLE)
           {
            ::Print(__FUNCTION__," > Открыт файл: ",path);
            ::Print(__FUNCTION__," > Строка 01: ",::FileReadString(filehandle));
            ::Print(__FUNCTION__," > Строка 02: ",::FileReadString(filehandle));
            ::Print(__FUNCTION__," > Строка 03: ",::FileReadString(filehandle));
           }
         //--- Закроем файл
         ::FileClose(filehandle);
        }
      ::Print("---");
     }
  }

Осталось только скомпилировать программу и загрузить её на график. Результат показан на скриншоте ниже. В вашем случае содержание файлового навигатора должно соответствовать содержимому файловой системы терминала на вашем компьютере.

 Рис. 1. Тест файлового навигатора.

Рис. 1. Тест файлового навигатора.


На скриншоте ниже показан ещё один пример с развёрнутой структурой древовидного списка файлового навигатора: 

 Рис. 2. Развёрнутая структура древовидного списка файлового навигатора.

Рис. 2. Развёрнутая структура древовидного списка файлового навигатора.


В случае с заложенной в нашего тестового эксперта обработкой события в пользовательском классе, при выделении файла в журнал будет выводиться следующий результат: 

2016.06.16 02:15:29.994         CProgram::OnEvent > Строка 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 > Строка 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 > Строка 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 > Открыт файл: 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

Всё работает так, как задумывалось!  

 

 


Заключение

На текущем этапе разработки библиотеки для создания графических интерфейсов её схема выглядит так, как показано на рисунке ниже:

Рис. 3. Структура библиотеки на текущей стадии разработки. 

Рис. 3. Структура библиотеки на текущей стадии разработки.

Мы завершили восьмую часть серии о создании графических интерфейсов в торговых терминалах MetaTrader. В ней были рассмотрены такие элементы, как статический и выпадающий календарь, древовидный список, указатель для курсора мыши и файловый навигатор. 

В следующей (девятой) части серии будем разбирать элементы управления, перечисленные в списке ниже:

  • Элементы для выбора цвета.
  • Индикатор выполнения.
  • Линейный график.

Ниже вы можете загрузить к себе на компьютер весь материал восьмой части серии, чтобы сразу получить возможность протестировать, как всё это работает. При возникновении вопросов по использованию материала, предоставленного в этих файлах, вы можете обратиться к подробному описанию процесса разработки библиотеки в одной из статей в представленном списке ниже или задать вопрос в комментариях к статье.

Список статей (глав) восьмой части:


Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (53)
Реter Konow
Реter Konow | 28 дек. 2016 в 10:59
Anatoli Kazharski:
У Вас есть возможность загрузить тестовые файлы к себе на компьютер и увидеть, как это работает. 

Внизу статьи только файлы библиотеки mqh.

А есть какой нибудь тестовый экзешник?

Просто, не очень понимаю как мне все это так скомпоновать, чтобы увидеть то, что хочу...

Наверное придется до конца изучить ООП, по-новой перечитать все статьи и придумать специальный советник для этого... В общем, - проблема...

Реter Konow
Реter Konow | 28 дек. 2016 в 11:00
Artyom Trishkin:
Так ведь и цели не было показывать то, что не вызывает нареканий. Показал лишь то, что не удобно с моей точки зрения.
Согласен. Логично.
Anatoli Kazharski
Anatoli Kazharski | 28 дек. 2016 в 11:08
Реter Konow:

Внизу статьи только файлы библиотеки mqh.

А есть какой нибудь тестовый экзешник?

Просто, не очень понимаю как мне все это так скомпоновать, чтобы увидеть то, что хочу...

Наверное придется до конца изучить ООП, по-новой перечитать все статьи и придумать специальный советник для этого... В общем, - проблема...

Да, там может возникнуть проблема, так как тестовые файлы из ранних статей уже неактуальны (по некоторым пунктам) для последней версии библиотеки. 

Дождитесь, когда опубликуется следующая статья. Это будет буквально на этой неделе. Там есть некоторые критические исправления, о которых сообщали пользователи библиотеки. И потом я выложу здесь тестовое MQL-приложение с древовидным списком.

Реter Konow
Реter Konow | 28 дек. 2016 в 11:10
Anatoli Kazharski:

Да, там может возникнуть проблема, так как тестовые файлы из ранних статей уже неактуальны (по некоторым пунктам) для последней версии библиотеки. 

Дождитесь, когда опубликуется следующая статья. Это будет буквально на этой неделе. Там есть некоторые критические исправления, о которых сообщали пользователи библиотеки. И потом я выложу здесь тестовое MQL-приложение с древовидным списком.

Отлично! ) Подождем-с...
Anatoli Kazharski
Anatoli Kazharski | 28 дек. 2016 в 15:57
Реter Konow:
Отлично! ) Подождем-с...

В архиве папка Test с файлами тестового MQL-приложения.

  • Расположите папку Test в директории MetaTrader 5\MQL5\Experts\.
  • Откройте главный файл приложения Test.mq5 в редакторе кода и скомпилируйте его.

Последнюю на текущий момент версию библиотеки (build 7) можно скачать в опубликованной сегодня статье: Графические интерфейсы X: Расширенное управление списками и таблицами. Оптимизация кода (build 7)

Результат на графике:

 

Работа с сокетами в MQL, или Как стать провайдером сигналов Работа с сокетами в MQL, или Как стать провайдером сигналов
Сокеты… Что вообще сейчас в нашем информационном мире может без них существовать? Впервые появившиеся в 1982 г. и практически не изменившиеся до настоящего времени, они исправно работают на нас каждую секунду. Это основа сети, нервные окончания нашей Matrix, в которой мы живем.
Текстовые файлы для хранения входных параметров советников, индикаторов и скриптов Текстовые файлы для хранения входных параметров советников, индикаторов и скриптов
В статье рассмотрены вопросы хранения динамических объектов, массивов и других переменных в качестве свойств советников, индикаторов и скриптов в текстовых файлах. Они служат удобным дополнением к функционалу стандартных средств, предлагаемых языками MQL.
Графические интерфейсы IX: Элемент "Палитра для выбора цвета" (Глава 1) Графические интерфейсы IX: Элемент "Палитра для выбора цвета" (Глава 1)
Этой статьей мы открываем девятую часть серии о разработке библиотеки для создания графических интерфейсов в среде торговых терминалов MetaTrader. Она состоит из двух глав, в которых представлены новые элементы управления и интерфейса: «Палитра для выбора цвета», «Кнопка для вызова цветовой палитры», «Индикатор выполнения» и «Линейный график».
Графические интерфейсы VIII: Элемент "Древовидный список" (Глава 2) Графические интерфейсы VIII: Элемент "Древовидный список" (Глава 2)
В предыдущей главе восьмой части серии о графических интерфейсах рассматривались элементы «Статический календарь» и «Выпадающий календарь». Вторую главу посвятим не менее сложному составному элементу, такому как «Древовидный список», без которого не обходится ни одна полноценная библиотека для создания графических интерфейсов. Представленная в этой статье реализация древовидного списка содержит в себе множество гибких настроек и режимов, что позволит максимально точно настроить этот элемент управления под свои нужды.