Do you like the article?
Share it with others -

Use new possibilities of MetaTrader 5

# Graphical Interfaces VIII: the File Navigator Control (Chapter 3)

18 August 2016, 09:48
0
4 304

### Introduction

The first article Graphical Interfaces I: Preparation of the Library Structure (Chapter 1) explains in detail what this library is for. You will find a list of articles with links at the end of each chapter. There, you can also download a complete version of the library at the current stage of development. The files must be placed in the same directories as they are located in the archive.

In the first and second chapters of the eighth part of the series, our library has been reinforced by several classes for developing mouse pointers (CPointer), calendars (CCalendar and CDropCalendar classes) and tree views (CTreeItem and CTreeView classes). In this article, we further develop the subject of the previous chapter, as well as examine the file navigator control.

### The File Navigator Control

File navigator is a kind of guide allowing you to see the elements of the file system hierarchically via a graphical interface of a program. Besides, it provides access to each element of the hierarchy allowing you to perform certain actions, like opening a file to view data, saving data to a file, moving a file, etc.

This article deals with the first version of the file navigator. It provides users with the following options:

• navigating the terminal's file "sandbox" within a graphical interface of an MQL application;
• finding necessary folders and files both in the terminals' common folder and the client terminal's local folder;
• saving a path for the subsequent programmatic access to a folder or a file selected in the file navigator.

Note:

Working with files is strictly controlled in MQL5 language for security reasons. Files being processed by means of MQL5 language are always located within the file "sandbox". A file is opened in the client terminal's directory in MQL5\Files (or testing_agent_directory\MQL5\Files in case of testing). If the FILE_COMMON flag is specified among other flags, the file is opened in the common folder of all client terminals \Terminal\Common\Files.

### Developing CFileNavigator Class

At the current stage of development, we already have all necessary tools for developing a file navigator in the library. The previously described tree view control is in fact a basis for creating file navigators. Apart from a tree view with contents area, we need to develop an address bar containing the full path relative to a currently selected element in the tree view.

Let's provide an opportunity to select the folders to be displayed in the root directory. For example, we can use only one of the terminal's file "sandbox" directories or provide access to two of them. To do this, add ENUM_FILE_NAVIGATOR_CONTENT enumeration to the Enums.mqh file (see the code listing below):

• FN_BOTH – show both directories.
• FN_ONLY_MQL – show directory of only the client terminal's local folder.
• FN_ONLY_COMMON – show directory of only the common folder of all installed terminals.

//+------------------------------------------------------------------+
//| Enumeration of the file navigator content                        |
//+------------------------------------------------------------------+
enum ENUM_FILE_NAVIGATOR_CONTENT
{
FN_BOTH        =0,
FN_ONLY_MQL    =1,
FN_ONLY_COMMON =2
};

Create the FileNavigator.mqh file with the CFileNavigator class and connect it to the library engine (WndContainer.mqh file):

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

The standard set of all method library elements should be implemented in the CFileNavigator class as shown in the listing below:

//+------------------------------------------------------------------+
//| Class for creating file navigator                                |
//+------------------------------------------------------------------+
class CFileNavigator : public CElement
{
private:
//--- Pointer to the form to which the element is attached
CWindow          *m_wnd;
//---
public:
CFileNavigator(void);
~CFileNavigator(void);
//--- Save the form pointer
void              WindowPointer(CWindow &object)                           { m_wnd=::GetPointer(object);                }
//---
public:
//--- Chart event handler
virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
//--- Timer
virtual void      OnEventTimer(void) {}
//--- Moving the element
virtual void      Moving(const int x,const int y);
//--- (1) Show, (2) hide, (3) reset, (4) delete
virtual void      Show(void);
virtual void      Hide(void);
virtual void      Reset(void);
virtual void      Delete(void);
//--- (1) Set, (2) reset priorities of the left mouse button press
virtual void      SetZorders(void);
virtual void      ResetZorders(void);
//--- Reset the color
virtual void      ResetColors(void) {}
};

Let's list the properties available to the library users for configuring the file navigator.

• tree view area width
• Contents area width
• Area background color
• Frame color
• Images for folders and files
• Navigator mode (Show all/Folders only)
• File navigator contents mode (Common folder/Local/All)

//+------------------------------------------------------------------+
//| Class for creating file navigator                                |
//+------------------------------------------------------------------+
class CFileNavigator : public CElement
{
private:
//--- Tree view area width
int               m_treeview_area_width;
//--- Content area width
int               m_content_area_width;
//--- Background and background frame color
color             m_area_color;
color             m_area_border_color;
//--- Images for (1) folders and (2) files
string            m_file_icon;
string            m_folder_icon;
//--- File navigator content mode
ENUM_FILE_NAVIGATOR_CONTENT m_navigator_content;
//--- Priorities of the left mouse button press
int               m_zorder;
//---
public:
//--- (1) Navigator mode (Show all/Folders only), (2) navigator content (Common folder/Local/All)
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) Address bar height, (2) width of the tree view and (3) the content list
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) Background and (2) background frame color
void              AreaBackColor(const color clr)                           { m_area_color=clr;                          }
void              AreaBorderColor(const color clr)                         { m_area_border_color=clr;                   }
//--- Color of (1) the background and (2) address bar text
//--- Set the file paths to the (1) files and (2) folders
void              FileIcon(const string file_path)                         { m_file_icon=file_path;                     }
void              FolderIcon(const string file_path)                       { m_folder_icon=file_path;                   }
};

The initialization of property fields by default values is performed in the class constructor (see the code listing below). Default folder and navigator file images are connected to the library as resources. They can be downloaded from the archive attached below.

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

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
m_treeview_area_width(300),
m_content_area_width(0),
m_navigator_content(FN_ONLY_MQL),
m_area_border_color(clrLightGray),
m_file_icon("Images\\EasyAndFastGUI\\Icons\\bmp16\\text_file.bmp"),
m_folder_icon("Images\\EasyAndFastGUI\\Icons\\bmp16\\folder.bmp")
{
//--- Store the name of the element class in the base class
CElement::ClassName(CLASS_NAME);
//--- Set priorities of the left mouse button click
m_zorder=0;
}

In order to create the file navigator, we need two private and one public method. The hierarchical system of the file structure is implemented using the CTreeView class described in the previous article. The file with this class should be included in the FileNavigator.mqh file.

//+------------------------------------------------------------------+
//|                                                FileNavigator.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
#include "TreeView.mqh"
//+------------------------------------------------------------------+
//| Class for creating file navigator                                |
//+------------------------------------------------------------------+
class CFileNavigator : public CElement
{
private:
//--- Objects for creating the element
CTreeView         m_treeview;
//---
public:
//--- Methods for creating file navigator
bool              CreateFileNavigator(const long chart_id,const int subwin,const int x,const int y);
//---
private:
bool              CreateTreeView(void);
};

Before describing the methods for the file navigator development in details, let's first examine the arranging of the terminal's hierarchical file system structure.

### Arranging the Hierarchical File System Structure

Before creating the file navigator, we should first scan the terminal's file system and save the parameters of all of its elements to develop a tree view. All these parameters have been examined in details in the previous article. Therefore, only an array list for the parameters to be saved for developing a tree view is displayed below.

• General index
• General index of a previous node
• Folder/file name
• Local index
• Node level
• Local index of the previous node
• Total number of elements in the folder
• Number of folders in the folder
• Folder flag
• Item status (minimized/unfolded)

In order to prepare the parameters, we need two array lists – main (g prefix) and auxiliary (l prefix):

class CFileNavigator : public CElement
{
private:
//--- Main arrays for data storage
int               m_g_list_index[];           // general index
int               m_g_prev_node_list_index[]; // general index of the previous node
string            m_g_item_text[];            // file/folder name
int               m_g_item_index[];           // local index
int               m_g_node_level[];           // node level
int               m_g_prev_node_item_index[]; // local index of the previous node
int               m_g_items_total[];          // total number of elements in folder
int               m_g_folders_total[];        // total number of folders in folder
bool              m_g_is_folder[];            // folder attribute
bool              m_g_item_state[];           // item state (minimized/open)
//--- Auxiliary arrays for data collection
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[];
};

We need a number of additional methods to scan the terminal file system, gather all the data and save it in the arrays. The CFileNavigator::AuxiliaryArraysResize() method is necessary for working with auxiliary arrays. It allows changing their size relative to the currently used node level (see the code level below). In other words, the array size exceeds the current node level value to be passed to the method as a single argument by one element. If the current node level value is 0, the array size is set to 1, if the node level is 1, the array size is 2, etc. Since the node level during the data collection may increase or decrease, the array size is changed accordingly. The same method is used to initialize the current node's array element.

class CFileNavigator : public CElement
{
private:
//--- Increase the array size by one element
void              AuxiliaryArraysResize(const int node_level);
};
//+------------------------------------------------------------------+
//| Increase the size of additional arrays by one element            |
//+------------------------------------------------------------------+
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);
//--- Initialize the last value
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;
}

The CFileNavigator::AddItem() method is used to add an element with parameters to the main arrays. Here, the arrays are increased by one element at each method call. The values of the passed arguments are saved to the last cell of the element.

class CFileNavigator : public CElement
{
private:
//--- Adds item to the 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);
};
//+------------------------------------------------------------------+
//| Add item with the specified parameters to 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)
{
//--- Increase the array size by one element
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);
//--- Store the value of passed parameters
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;
}

When scanning the file system, it is necessary to move to each detected folder to view its contents. To achieve this, the CFileNavigator::IsFolder() method is used. It defines whether the current element is a folder or a file. The file system element name should be passed to it as a single parameter. If "\" character is detected in the element name, this means the method returns true. If this is a file, the method returns false

The FileIsExist() system function is another way to check whether the element is a file. The function returns true if the passed element name in the last search handle is a file. If this is a directory, the function generates the ERR_FILE_IS_DIRECTORY error.

class CFileNavigator : public CElement
{
private:
//--- Determines if a file or folder name was passed
bool              IsFolder(const string file_name);
};
//+------------------------------------------------------------------+
//| Determines if a file or folder name was passed                   |
//+------------------------------------------------------------------+
bool CFileNavigator::IsFolder(const string file_name)
{
//--- If the name contains "\\", characters, then it is a folder
if(::StringFind(file_name,"\\",0)>-1)
return(true);
//---
return(false);
}

In order to form a tree view, specify the number of elements in the item, as well as the number of elements that are folders. Therefore, we need the appropriate methods for defining the parameter values. The methods are CFileNavigator::ItemsTotal() and CFileNavigator::FoldersTotal(). They are similar, though in the second one the counter is increased only if a checked element is a folder. Two arguments are passed to the both methods: (1) path and (2) search area. The search area is a common folder of all terminals or a terminal local folder. Next, the FileFindFirst() system function is used in an attempt to receive the search handle at the specified path, as well as the first element name (if found) simultaneously. The valid handle and the object name indicate that the first element is found.

Next, the FileFindNext() system function is used in a cycle, and attempts are made to access all other elements in the same directory one at a time. The previosuly obtained handle is used as the directory key. If the element is found, the function returns true and the counter is increased. The search is stopped as soon as the function returns false. The search handle should be closed at the end of the both methods. The FileFindClose() system function is used for that.

class CFileNavigator : public CElement
{
private:
//--- Returns the number of (1) items and (2) folders in the specified directory
int               ItemsTotal(const string search_path,const int mode);
int               FoldersTotal(const string search_path,const int mode);
};
//+------------------------------------------------------------------+
//| Count the number of files in the current directory               |
//+------------------------------------------------------------------+
int CFileNavigator::ItemsTotal(const string search_path,const int search_area)
{
int    counter       =0;              // item counter
string file_name     ="";             // file name
long   search_handle =INVALID_HANDLE; // search handle
//--- Get the first file in the current directory
search_handle=::FileFindFirst(search_path,file_name,search_area);
//--- If the directory is not empty
if(search_handle!=INVALID_HANDLE && file_name!="")
{
//--- Count the number of objects in the current directory
counter++;
while(::FileFindNext(search_handle,file_name))
counter++;
}
//--- Close the search handle
::FileFindClose(search_handle);
return(counter);
}
//+------------------------------------------------------------------+
//| Count the number of folders in the current directory             |
//+------------------------------------------------------------------+
int CFileNavigator::FoldersTotal(const string search_path,const int search_area)
{
int    counter       =0;              // item counter
string file_name     ="";             // file name
long   search_handle =INVALID_HANDLE; // search handle
//--- Get the first file in the current directory
search_handle=::FileFindFirst(search_path,file_name,search_area);
//--- If not empty, count the number of objects in the current directory in a loop
if(search_handle!=INVALID_HANDLE && file_name!="")
{
//--- If this is a folder, increase the counter
if(IsFolder(file_name))
counter++;
//--- Iterate over the list further and count the other folders
while(::FileFindNext(search_handle,file_name))
{
if(IsFolder(file_name))
counter++;
}
}
//--- Close the search handle
::FileFindClose(search_handle);
return(counter);
}

When collecting the parameters of the file system elements, we need to receive the local indices of the previous nodes. The CFileNavigator::PrevNodeItemIndex() method is used for that. (1) The current item index in the root directory and (2) the current node level should be passed to it as arguments. The values of both arguments are managed by the counters in the external cycles of the main methods, inside which the method is called.

class CFileNavigator : public CElement
{
private:
//--- Return the local index of the previous node relative to the passed parameters
int               PrevNodeItemIndex(const int root_index,const int node_level);
};
//+------------------------------------------------------------------+
//| Return the local index of the previous node                      |
//| relative to the passed parameters                                |
//+------------------------------------------------------------------+
int CFileNavigator::PrevNodeItemIndex(const int root_index,const int node_level)
{
int prev_node_item_index=0;
//--- If not the root directory
if(node_level>1)
prev_node_item_index=m_l_item_index[node_level-1];
else
{
//--- If not the first item in the list
if(root_index>0)
prev_node_item_index=m_l_item_index[node_level-1];
}
//--- Return the local index of the previous node
return(prev_node_item_index);
}

Each time a folder is found, transition to it (i.e. on the next node level) is performed. The CFileNavigator::ToNextNode() method is used for that. Some parameter arguments are passed by the link to provide the ability to manage their values

Here, the path for calculating the directory elements is formed at the very start of the method. Then, the element parameters are saved to the auxiliary arrays by the current node level index. After that, we need to obtain the previous node's item index and add the item with the specified parameters to the general arrays. In other words, this is exactly the place where the folder we are in is added to the arrays having the parameters prepared for it.

After that, the node counter is increased and the size of auxiliary arrays is corrected relative to it. Next, we should save a few parameters for the new node level: (1) path, (2) number of elements and (3) number of folders. At the end of the method, (1) the counter of local indices is reset and (2) the current search handle is closed.

class CFileNavigator : public CElement
{
private:
//--- Go to the next node
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);
};
//+------------------------------------------------------------------+
//| Go to the next node                                              |
//+------------------------------------------------------------------+
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)
{
//--- Search filter (* - check all files/folders)
string filter="*";
//--- Generate the path
string search_path=m_l_path[node_level]+item_text+filter;
//--- Get and store data
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;
//--- Get the index of the previous node item
int prev_node_item_index=PrevNodeItemIndex(root_index,node_level);
//--- Add item with the specified data to the general arrays
item_index,m_l_item_total[node_level],m_l_folders_total[node_level],true);
//--- Increase the node counter
node_level++;
//--- Increase the array size by one element
AuxiliaryArraysResize(node_level);
//--- Get and store data
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);
//--- Zero the counter of local indices
item_index=0;
//--- Close the search handle
::FileFindClose(handle);
}


Now, let's consider the main cycle, in which the main actions are performed. For more convenience, the cycle is located in the separate method CFileNavigator::FileSystemScan(). The reading of the terminal file system and saving the parameters of the detected elements to the main arrays are performed in the method. These arrays will be later used to construct a tree view. The cycle works till the algorithm reaches the end of the list or the program is deleted from the chart.

The algorithm works the following way. The check for the beginning of the list (the first element of the list) of the current directory is performed at the cycle start. If the checked element is actually new, we receive a handle and a name of the first element detected in the specified area of the file system and save the number of elements and folders in the current node level of the auxiliary arrays.

If this is not the first index, the sequence of the local indices in the current node is checked. If the node index has already been present, the counter of local indices is increased and transition to the next element in the next directory is performed.

If the algorithm has reached the next code block, check if we exceed the range of the element list related to the root node. If this is the case, the cycle is stopped also meaning that the search handle (outside of the cycle block) closes and the program exits the method. If, instead, we have reached the end of any other node list except the root one, then we need to move to the next level. Here, (1) the node counter is decreased one level back, (2) the local indices counter is reset, (3) the search handle is closed and (4) transition to the next cycle iteration is performed.

If not a single condition in the previous if-else construction is fulfilled, check if the current file system element is a folder. If yes, the previously described CFileNavigator::ToNextNode() method is used to move to the next level. After that, the total indices counter is increased and command for moving to the next iteration is activated.

If the file system element is a file, then first we will receive a local index of the previous node. Then we should add the item with the specified parameters to the general arrays. Increase the total and local indices counters. As a final cycle operation, transition to the next terminal file system element is performed. After that, the next cycle iteration starts, and the algorithm passes through all the conditions described above.

class CFileNavigator : public CElement
{
private:
//--- Read the file system and write parameters to arrays
void              FileSystemScan(const int root_index,int &list_index,int &node_level,int &item_index,int search_area);
};
//+------------------------------------------------------------------+
//| Reads the file system and writes item parameters                 |
//| in 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; // Folder/file search handle
string file_name     ="";             // Name of the found item (file/folder)
string filter        ="*";            // Search filter (* - check all files/folders)
//--- Scan the directories and store data in arrays
while(!::IsStopped())
{
//--- If this is the beginning of the directory list
if(item_index==0)
{
//--- Path for searching for all items
string search_path=m_l_path[node_level]+filter;
//--- Get the handle and name of the first file
search_handle=::FileFindFirst(search_path,file_name,search_area);
//--- Get the number of files and folders in the specified directory
m_l_item_total[node_level]    =ItemsTotal(search_path,search_area);
m_l_folders_total[node_level] =FoldersTotal(search_path,search_area);
}
//--- If the index of this node had already been used, go to the next file
if(m_l_item_index[node_level]>-1 && item_index<=m_l_item_index[node_level])
{
//--- Increase the counter of local indices
item_index++;
//--- Go to the next item
::FileFindNext(search_handle,file_name);
continue;
}
//--- If reached the end of list in the root node, end the loop
if(node_level==1 && item_index>=m_l_item_total[node_level])
break;
//--- If reached the end of list in any node, except the root node
else if(item_index>=m_l_item_total[node_level])
{
//--- Set the node counter one level back
node_level--;
//--- Zero the counter of local indices
item_index=0;
//--- Close the search handle
::FileFindClose(search_handle);
continue;
}
//--- If this is folder
if(IsFolder(file_name))
{
//--- Go to the next node
ToNextNode(root_index,list_index,node_level,item_index,search_handle,file_name,search_area);
//--- Increase the counter of general indices and start a new iteration
list_index++;
continue;
}
//--- Get the local index of the previous node
int prev_node_item_index=PrevNodeItemIndex(root_index,node_level);
//--- Add item with the specified data to the general arrays
//--- Increase the counter of general indices
list_index++;
//--- Increase the counter of local indices
item_index++;
//--- Go to the next element
::FileFindNext(search_handle,file_name);
}
//--- Close the search handle
::FileFindClose(search_handle);
}

Now, let's consider the main method CFileNavigator::FillArraysData(), in which all methods described above are called.

First of all, the sequence of the terminal's common and local folder directories is set here. This sequence depends on the mode (ENUM_FILE_NAVIGATOR_CONTENT) specified in the file navigator properties. By default, the sequence is set so that the common terminal folder comes first in the list, while the terminal's local folder comes second. This works only in case FN_BOTH ("display contents of both directories") mode is set. If the mode "show the contents of one directory" is selected, the beginning (begin) and the end (end) of the cycle range are initialized appropriately.

After the search area at the beginning of the cycle body is defined, the following steps are performed one after another.

• The local indices counter is reset
• The size of the auxiliary arrays is changed relative to the current node level
• The number of elements and folders in the current directory is saved to the first index of the same arrays
• The element with the specified parameters is added to the main arrays. Since it is used as the root directory here, the name of the current element can be from one of the two directories: Common\\Files\\ or MQL5\\Files\\
• The general indices and node level counters are increased
• The size of the auxiliary arrays is again changed relative to the current node level
• If the search area is currently located in the terminal's local folder, the values for the first indices of the auxiliary arrays: (1) local indices and (2) the previous node's general indices, are corrected.

Finally, the CFileNavigator::FileSystemScan() method for reading the file system in the specified search area and saving the parameters of its elements to the main arrays for forming a tree view is called.

class CFileNavigator : public CElement
{
private:
//--- Fills arrays with parameters of the terminal file system elements
void              FillArraysData(void);
};
//+------------------------------------------------------------------+
//| Fills arrays with parameters of the file system elements         |
//+------------------------------------------------------------------+
void CFileNavigator::FillArraysData(void)
{
//--- Counters of (1) general indices, (2) node levels, (3) local indices
int list_index =0;
int node_level =0;
int item_index =0;
//--- If both directories should be displayed (Common (0)/Local (1))
int begin=0,end=1;
//--- If only the local directory content should be displayed
if(m_navigator_content==FN_ONLY_MQL)
begin=1;
//--- If only the common directory contents should be displayed
else if(m_navigator_content==FN_ONLY_COMMON)
begin=end=0;
//--- Iterate over the specified directories
for(int root_index=begin; root_index<=end; root_index++)
{
//--- Determine the directory for scanning the file structure
int search_area=(root_index>0)? 0 : FILE_COMMON;
//--- Reset the counter of the local indices
item_index=0;
//--- Increase the array size by one element (relative to the node level)
AuxiliaryArraysResize(node_level);
//--- Get the number of files and folders in the specified directory (* - scan all files/folders)
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);
//--- Add item with the name of the root directory to the top of the list
string item_text=(root_index>0)? "MQL5\\Files\\" : "Common\\Files\\";
//--- Increase the counters of general indices and node levels
list_index++;
node_level++;
//--- Increase the array size by one element (relative to the node level)
AuxiliaryArraysResize(node_level);
//--- Initialize the first items for the directory of the local folder of the terminal
if(root_index>0)
{
m_l_item_index[0]           =root_index;
m_l_prev_node_list_index[0] =list_index-1;
}
//--- Scan the directories and store data in arrays
FileSystemScan(root_index,list_index,node_level,item_index,search_area);
}
}

### Methods for Creating the Control

The CFileNavigator::FillArraysData() method is called only once before creating the File navigator control. In fact, a library user may not even be aware of it. It is only necessary to specify some common properties affecting the look and contents of the file navigator.

//+------------------------------------------------------------------+
//| Create file navigator                                            |
//+------------------------------------------------------------------+
bool CFileNavigator::CreateFileNavigator(const long chart_id,const int subwin,const int x,const int y)
{
//--- Leave, if there is no form pointer
if(::CheckPointer(m_wnd)==POINTER_INVALID)
{
::Print(__FUNCTION__," > Before creating the file navigator, "
"the pointer to the form should be passed to it: CFileNavigator::WindowPointer(CWindow &object).");
return(false);
}
//--- Scan the file system of the terminal and store data in arrays
FillArraysData();
//--- Initializing variables
m_id       =m_wnd.LastId()+1;
m_chart_id =chart_id;
m_subwin   =subwin;
m_x        =x;
m_y        =y;
//--- Margins from the edge
CElement::XGap(CElement::X()-m_wnd.X());
CElement::YGap(CElement::Y()-m_wnd.Y());
//--- Creating an element
return(false);
if(!CreateTreeView())
return(false);
//--- Hide the element if the window is a dialog one or is minimized
if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized())
Hide();
//---
return(true);
}

Creating the address bar is the first step when developing the file navigator. This will be a single object of OBJ_BITMAP_LABEL type. Its contents is drawn in full. Previously, we have already considered examples of creating controls drawn on canvas. Therefore, here we will consider only the nuances related to the developed control.

In order to draw an address bar, we need two methods:

• The CFileNavigator::Border() method is used to draw the address bar frame.
• CFileNavigator::UpdateAddressBar() is the main method for drawing and displaying the latest changes, including the directory selected in the tree view.

Let's consider only the CFileNavigator::UpdateAddressBar() method code here since the frame has already been described when developing other controls, for example, in the article Graphical Interfaces IV: Informational Interface Elements (Chapter 1)

We have already mentioned that users can specify the address bar background color and its size by the Y axis before creating the file navigator. The text in the canvas area will be positioned with the 5 pixel indent by the X axis from the left margin, while positioning along the Y axis should be centered. Since we have the size along the Y axis, we simply need to divide the address bar height by 2 to obtain the Y coordinate. In order to call the TextOut() method to draw a text on the canvas, we also need to pass the flags for defining the anchor type to the left and at the center

During the first installation of the file navigator, the path is not initialized yet, and the m_current_path class field for its storage contains the blank string. Since the file system elements can be quite numerous, forming the arrays and creating the tree view may take some time. Since the address bar is created first, it can feature a message that prompts users to wait a bit. For example, here it will look as follows: "Loading. Please wait...".

In the end of the method, the canvas is updated in order to display the most recent changes.

class CFileNavigator : public CElement
{
private:
//--- Draws a border for the address bar
void              Border(void);
//--- Displays the current path in the address bar
};
//+------------------------------------------------------------------+
//| Displays the current path in the address bar                     |
//+------------------------------------------------------------------+
{
//--- Coordinates
int x=5;
//--- Clear background
//--- Draw the background frame
Border();
//--- Text properties
//--- If the path is not set, show the default string
if(m_current_full_path=="")
//--- Output the path to the address bar of the file navigator
//--- Update the canvas for drawing
}

The address bar width is calculated before its creation in the CFileNavigator::CreateAddressBar() method. If the contents area is disabled in the settings, the address bar width is equal to the tree view one. In other cases, it is calculated using the principle implemented in the tree view class (CTreeView) for the common control width.

After creating the object, the CFileNavigator::UpdateAddressBar() method is called for drawing the background, frame and default message.

//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
{
//--- Forming the object name
//--- Coordinates
int x =CElement::X();
int y =CElement::Y();
//--- Sizes:
//    Calculate the width
int x_size=0;
//--- If there is no content area
if(m_content_area_width<0)
x_size=m_treeview_area_width;
else
{
//--- If a specific width of the content area is defined
if(m_content_area_width>0)
x_size=m_treeview_area_width+m_content_area_width-1;
//--- If the right edge of the content area must be at the right edge of the form
else
x_size=m_wnd.X2()-x-2;
}
//--- Height
//--- Creating the object
return(false);
//--- Attach to the chart
return(false);
//--- Set the properties
//--- Store the size
CElement::X(x);
CElement::Y(y);
//--- Store the size
CElement::XSize(x_size);
CElement::YSize(y_size);
//--- Margins from the edge
//--- Store the object pointer
//--- Hide the element if the window is a dialog one or is minimized
if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized())
//---
return(true);
}

Now, we have reached the stage when the CFileNavigator::CreateTreeView() method is called for setting the tree view. In the previous article, I pointed out that before creating the CTreeView type control, we first need to add the items with the array parameters to the control's arrays. At this stage, all parameters for the items are placed to the CFileNavigator main class arrays. All we have to do now is to pass them to the tree view class in the cycle.

The image for each item is defined in the same cycle. Besides, the last character (‘\’) should be deleted in the names of the folders.

//+------------------------------------------------------------------+
//| Creates the tree view                                            |
//+------------------------------------------------------------------+
bool CFileNavigator::CreateTreeView(void)
{
//--- Store the window pointer
m_treeview.WindowPointer(m_wnd);
//--- set properties
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);
//--- Form the tree view arrays
int items_total=::ArraySize(m_g_item_text);
for(int i=0; i<items_total; i++)
{
//--- Set icon for the item (folder/file)
string icon_path=(m_g_is_folder[i])? m_folder_icon : m_file_icon;
//--- If it is a folder, delete the last character ('\') in the string
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);
//--- Add item to the tree view
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]);
}
//--- Create the tree view
return(false);
//---
return(true);
}

### Event Handler

(1) The full path of the directory selected in tree list (including a hard drive volume label) relative to the file system, (2) the path relative to the terminal sandbox and (3) the current directory area are saved in the class fields. The appropriate methods are needed to obtain these values. Besides, we will need the CFileNavigator::SelectedFile() method to obtain the selected element which is a file.

class CFileNavigator : public CElement
{
private:
//--- Current path relative to the file "sandbox" of the terminal
string            m_current_path;
//--- Current path relative to the file system, including the hard drive volume label
string            m_current_full_path;
//--- Area of the current directory
int               m_directory_area;
//---
public:
//--- Returns (1) the current path and (2) the full path, (3) the selected file
string            CurrentPath(void)                                  const { return(m_current_path);                    }
string            CurrentFullPath(void)                              const { return(m_current_full_path);               }
//--- Returns (1) directory area and (2) the selected file
int               DirectoryArea(void)                                const { return(m_directory_area);                  }
string            SelectedFile(void)                                 const { return(m_treeview.SelectedItemFileName()); }
};

The file navigator event handler is configured to accept only one event having the ON_CHANGE_TREE_PATH ID. It is generated by a tree view when selecting an item in its structure. The CFileNavigator::OnChangeTreePath() method has been implemented to handle a message with this ID.

First, we receive the path saved in the tree view. After that, depending on the path category (common or local), we (1) receive the address of the root data folder and (2) form short and long paths and save the directory area flag

class CFileNavigator : public CElement
{
private:
//--- Handle event of selecting a new path in the tree view
void              OnChangeTreePath(void);
};
//+------------------------------------------------------------------+
//| Handle event of selecting a new path in the tree view            |
//+------------------------------------------------------------------+
void CFileNavigator::OnChangeTreePath(void)
{
//--- Get the current path
string path=m_treeview.CurrentFullPath();
//--- If this is the terminals common folder
if(::StringFind(path,"Common\\Files\\",0)>-1)
{
//--- Get the address of the terminals common folder
string common_path=::TerminalInfoString(TERMINAL_COMMONDATA_PATH);
//--- Delete the "Common\" prefix in the string (received in the event)
path=::StringSubstr(path,7,::StringLen(common_path)-7);
//--- Generate the path (short and full version)
m_current_path      =::StringSubstr(path,6,::StringLen(path)-6);
m_current_full_path =common_path+"\\"+path;
//--- Store the directory area
m_directory_area=FILE_COMMON;
}
//--- If this is the local folder of the terminal
else if(::StringFind(path,"MQL5\\Files\\",0)>-1)
{
//--- Get the address of data in the local folder of the terminal
string local_path=::TerminalInfoString(TERMINAL_DATA_PATH);
//--- Generate the path (short and full version)
m_current_path      =::StringSubstr(path,11,::StringLen(path)-11);
m_current_full_path =local_path+"\\"+path;
//--- Store the directory area
m_directory_area=0;
}
//--- Display the current path in the address bar
}

As a result, when the ON_CHANGE_TREE_PATH event arrives, the CFileNavigator::OnChangeTreePath() method should be called in the control event handler as shown in the code listing below:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CFileNavigator::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
{
//--- Handle event of "Change in the path of the tree view"
if(id==CHARTEVENT_CUSTOM+ON_CHANGE_TREE_PATH)
{
OnChangeTreePath();
return;
}
}

The event with the same ID can be accepted in the custom class handler. The example is discussed below.

### Integrating the Control into the Library Engine

For the correct control operation, it should be integrated into the library engine. The additions are mainly to be inserted into the CWndContainer base class in the WndContainer.mqh file the files of all other library elements are connected to. We should add the following:

• private array for the file navigator;
• method for receiving the number of file navigator applications of this type in the graphical interface (CFileNavigator);
• method for saving the pointers to the file navigator elements to the database.

The short version of the CWndContainer class (only the things that should be added) is provided in the code listing below:

#include "FileNavigator.mqh"
//+------------------------------------------------------------------+
//| Class for storing all interface objects                          |
//+------------------------------------------------------------------+
class CWndContainer
{
protected:
//--- Window array
CWindow          *m_windows[];
//--- Structure of element arrays
struct WindowElements
{
//--- File navigators
CFileNavigator   *m_file_navigators[];
};
//--- Array of element arrays for each window
WindowElements    m_wnd[];
//---
public:
//--- The number of file navigators
int               FileNavigatorsTotal(const int window_index);
//---
private:
//--- Stores pointers to the tree view controls in the base
};

You can examine the code of the methods in details in the attached files below.

### Testing the File Navigator

Now, everything is ready for testing the file navigator. Let's create a copy of the EA from the previous article and delete all elements except the main menu and the status bar from the user class (CProgram). In order to quickly test the main file editor contents modes, let's create two external EA parameters like shown in the code listing below. These types of enumerations and modes have already been considered in details in the article sections above.

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

Now, we need to declare the CFileNavigator file navigator class instance, as well as the method for creating an element and the margins from the edge of the form the instance is to be connected to.

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

The CProgram::CreateFileNavigator() method code for creating the file navigator is provided in the code listing below. We will set the navigator height to 10 points. Their default size (20 pixels) is specified by CFileNavigator class constructor. As noted above, the contents modes will be managed by external parameters. The look of the components can be configured by methods that can be received by the pointer. The code below illustrates this by using the example of receiving the tree view pointers and its scroll bars. The method call should be placed in the main method for creating the graphical interface

//+------------------------------------------------------------------+
//| Create an expert panel                                           |
//+------------------------------------------------------------------+
bool CProgram::CreateExpertPanel(void)
{
//--- Creating form 1 of the controls
//--- Creating controls:
//--- Creating the status bar
//--- Creating the file navigator
if(!CreateFileNavigator())
return(false);
//--- Redrawing the chart
m_chart.Redraw();
return(true);
}
//+------------------------------------------------------------------+
//| Create file navigator                                            |
//+------------------------------------------------------------------+
bool CProgram::CreateFileNavigator(void)
{
//--- Save the pointer to the form
m_navigator.WindowPointer(m_window1);
//--- Coordinates
int x=m_window1.X()+NAVIGATOR1_GAP_X;
int y=m_window1.Y()+NAVIGATOR1_GAP_Y;
//--- Set the properties before creating
m_navigator.TreeViewPointer().VisibleItemsTotal(10);
m_navigator.NavigatorMode(NavigatorMode);
m_navigator.NavigatorContent(NavigatorContent);
m_navigator.TreeViewAreaWidth(250);
//--- Scroll bar properties
m_navigator.TreeViewPointer().GetScrollVPointer().AreaBorderColor(clrLightGray);
m_navigator.TreeViewPointer().GetContentScrollVPointer().AreaBorderColor(clrLightGray);
//--- Create the control
if(!m_navigator.CreateFileNavigator(m_chart_id,m_subwin,x,y))
return(false);
//--- Add the pointer to the control to the database
return(true);
}

As an example, let's make the event handler log to show both full and short paths, as well as the name of a currently selected file. If the file is selected, we will open it and read the first three lines passing them to the log. Please note that we use the CFileNavigator::DirectoryArea() method to obtain the flag corresponding to the file location so that we are able to manage the directory area.

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
{
//--- Process the event "Changing the path in the tree view"
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 the file is selected, read it (the first three lines)
if(m_navigator.SelectedFile()!="")
{
//--- Form the path to the file
string path=m_navigator.CurrentPath()+m_navigator.SelectedFile();
//--- Receive the handle of the specified file
if(filehandle!=INVALID_HANDLE)
{
::Print(__FUNCTION__," > Opened file: ",path);
}
//--- Close the file
::FileClose(filehandle);
}
::Print("---");
}
}

Now, it is time to compile the program and load it to the chart. The result is shown on the screenshot below. In your case, the contents of the file navigator should match the contents of the terminal file system on your PC.

Fig. 1. Testing the file navigator

The screenshot below shows the unfolded file navigator tree view:

Fig. 2. Unfolded structure of the file navigator tree view

When dealing with the custom class event handler built into our test EA, the following result is obtained after the file is passed to the log:

2016.06.16 02:15:29.994         CProgram::OnEvent > Line 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 > Line 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 > Line 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 > Opened file: 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

All works fine!

### Conclusion

Schematic of the library for creating graphical interfaces at the current stage of development looks as shown below:

Fig. 3. Library structure at the current stage of development

This is the end of the eighth part of the series about creating graphical interfaces in the MetaTrader trading terminals. In this part, we have considered such controls as static and drop down calendar, tree view, mouse pointer and file navigator.

The next (ninth) part of the series will consider the following controls:

• Color selection.
• Progress bar.
• Line chart.

You can download the material of the entire eighth part below to perform tests. If you have questions on using the material presented in those files, you can refer to the detailed description of the library development in one of the articles from the list below or ask your question in the comments to this article.

List of articles (chapters) of part 8:

Translated from Russian by MetaQuotes Software Corp.
Original article: https://www.mql5.com/ru/articles/2541

Attached files |
Step on New Rails: Custom Indicators in MQL5

I will not list all of the new possibilities and features of the new terminal and language. They are numerous, and some novelties are worth the discussion in a separate article. Also there is no code here, written with object-oriented programming, it is a too serous topic to be simply mentioned in a context as additional advantages for developers. In this article we will consider the indicators, their structure, drawing, types and their programming details, as compared to MQL4. I hope that this article will be useful both for beginners and experienced developers, maybe some of them will find something new.

Here Comes the New MetaTrader 5 and MQL5

This is just a brief review of MetaTrader 5. I can't describe all the system's new features for such a short time period - the testing started on 2009.09.09. This is a symbolical date, and I am sure it will be a lucky number. A few days have passed since I got the beta version of the MetaTrader 5 terminal and MQL5. I haven't managed to try all its features, but I am already impressed.

Using text files for storing input parameters of Expert Advisors, indicators and scripts

The article describes the application of text files for storing dynamic objects, arrays and other variables used as properties of Expert Advisors, indicators and scripts. The files serve as a convenient addition to the functionality of standard tools offered by MQL languages.

How to create an indicator of non-standard charts for MetaTrader Market

Through offline charts, programming in MQL4, and reasonable willingness, you can get a variety of chart types: "Point & Figure", "Renko", "Kagi", "Range bars", equivolume charts, etc. In this article, we will show how this can be achieved without using DLL, and therefore such "two-for-one" indicators can be published and purchased from the Market.