
Graphical Interfaces VIII: the File Navigator Control (Chapter 3)
Contents
- Introduction
- The File Navigator Control
- Developing CFileNavigator Class
- Arranging the Hierarchical File System Structure
- Methods for Creating the Control
- Event Handler
- Integrating the Control into the Library Engine
- Testing the File Navigator
- Conclusion
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
- Address bar background color
- Address bar text color
- Address bar height
- 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; //--- Address bar background color color m_address_bar_back_color; //--- Address bar text color color m_address_bar_text_color; //--- Address bar height int m_address_bar_y_size; //--- 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 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) 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 void AddressBarBackColor(const color clr) { m_address_bar_back_color=clr; } void AddressBarTextColor(const color clr) { m_address_bar_text_color=clr; } //--- 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 | //+------------------------------------------------------------------+ 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") { //--- 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 CRectCanvas m_address_bar; 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 CreateAddressBar(void); 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 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); //--- 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 AddItem(list_index,file_name,node_level,prev_node_item_index,item_index,0,0,false); //--- 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\\"; AddItem(list_index,item_text,0,0,root_index,m_l_item_total[0],m_l_folders_total[0],true); //--- 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 if(!CreateAddressBar()) 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 void UpdateAddressBar(void); }; //+------------------------------------------------------------------+ //| Displays the current path in the address bar | //+------------------------------------------------------------------+ void CFileNavigator::UpdateAddressBar(void) { //--- Coordinates int x=5; int y=m_address_bar_y_size/2; //--- Clear background m_address_bar.Erase(::ColorToARGB(m_address_bar_back_color,0)); //--- Draw the background frame Border(); //--- Text properties m_address_bar.FontSet("Calibri",14,FW_NORMAL); //--- If the path is not set, show the default string if(m_current_full_path=="") m_current_full_path="Loading. Please wait..."; //--- Output the path to the address bar of the file navigator m_address_bar.TextOut(x,y,m_current_path,::ColorToARGB(m_address_bar_text_color),TA_LEFT|TA_VCENTER); //--- Update the canvas for drawing m_address_bar.Update(); }
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.
//+------------------------------------------------------------------+ //| Create address bar | //+------------------------------------------------------------------+ bool CFileNavigator::CreateAddressBar(void) { //--- Forming the object name string name=CElement::ProgramName()+"_file_navigator_address_bar_"+(string)CElement::Id(); //--- 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 int y_size=m_address_bar_y_size; //--- Creating the object if(!m_address_bar.CreateBitmapLabel(m_chart_id,m_subwin,name,x,y,x_size,y_size,COLOR_FORMAT_XRGB_NOALPHA)) return(false); //--- Attach to the chart if(!m_address_bar.Attach(m_chart_id,name,m_subwin,1)) return(false); //--- Set the properties m_address_bar.Background(false); m_address_bar.Z_Order(m_zorder); m_address_bar.Tooltip("\n"); //--- Store the size CElement::X(x); CElement::Y(y); //--- Store the size CElement::XSize(x_size); CElement::YSize(y_size); //--- Margins from the edge m_address_bar.XGap(x-m_wnd.X()); m_address_bar.YGap(y-m_wnd.Y()); //--- Update the address bar UpdateAddressBar(); //--- Store the object pointer CElement::AddToArray(m_address_bar); //--- Hide the element if the window is a dialog one or is minimized if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized()) m_address_bar.Timeframes(OBJ_NO_PERIODS); //--- 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_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]); } //--- Create the tree view if(!m_treeview.CreateTreeView(m_chart_id,m_subwin,m_x,m_y+m_address_bar_y_size)) 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 UpdateAddressBar(); }
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 bool AddFileNavigatorElements(const int window_index,CElement &object); };
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: // Main menu //--- Context menus //--- 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); m_navigator.AddressBarBackColor(clrWhite); m_navigator.AddressBarTextColor(clrSteelBlue); //--- 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 CWndContainer::AddToElementsArray(0,m_navigator); 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 int filehandle=::FileOpen(path,FILE_READ|FILE_TXT|FILE_ANSI|m_navigator.DirectoryArea(),'\n'); //--- If the handle is received, read the first three lines if(filehandle!=INVALID_HANDLE) { ::Print(__FUNCTION__," > Opened file: ",path); ::Print(__FUNCTION__," > Line 01: ",::FileReadString(filehandle)); ::Print(__FUNCTION__," > Line 02: ",::FileReadString(filehandle)); ::Print(__FUNCTION__," > Line 03: ",::FileReadString(filehandle)); } //--- 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:
- Graphical Interfaces VIII: the Calendar Control (Chapter 1)
- Graphical Interfaces VIII: the Tree View Control (Chapter 2)
- Graphical interface VIII: the File Navigator Control (Chapter 3)
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/2541





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use