Download MetaTrader 5

Graphical Interfaces VII: The Tabs Control (Chapter 2)

4 August 2016, 15:28
Anatoli Kazharski
1
5 001

Contents


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.

The first chapter of seventh part introduced three classes of controls for creating tables: text label table (CLabelsTable), edit box table (CTable) and rendered table (CCanvasTable). In this article (chapter two) we are going to consider the Tabs control. Two classes will be presented for this control - simple and with extended functionality.

 


Tabs control

Tabs are used to control the display of predefined sets of graphical interface controls. Often, multi-functional applications require a large number of controls to be fit into a confined space allocated for the graphical interface. Tabs can be used to group the controls by categories and to display only the currently needed group. This makes the interface much more accessible and more intuitive for the end user. On the surface, the tabs look like a group of buttons with labels (name of the controls group). At the same time, only one of them can be selected (active).

Let us enumerate all components of this control.

  1. Background or the area to fit a group of controls
  2. Tabs

 Fig. 1. Components of the «Tabs» control.

Fig. 1. Components of the Tabs control.

Let us create four modes to position the tabs relative to the area, where other controls will be placed: top, bottom, left and right. 

 


Developing a class for creating the Tabs control

Create the Tabs.mqh file and include it in the WndContainer.mqh file of the library:

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

The CTabs must be created in the Tabs.mqh file. In this class, just like in classes of other controls, it is necessary to create standard methods, as well as methods for storing the pointer to the form, to which this control will be attached.

//+------------------------------------------------------------------+
//| Class for creating tabs                                          |
//+------------------------------------------------------------------+
class CTabs : public CElement
  {
private:
   //--- Pointer to the form to which the element is attached
   CWindow          *m_wnd;
   //---
public:
                     CTabs(void);
                    ~CTabs(void);
   //--- (1) Stores the form pointer, (2) returns pointers to scrollbars
   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) {}
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTabs::CTabs(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CTabs::~CTabs(void)
  {
  }

Tab properties can be divided into unique and common ones. Let us enumerate both lists.

Unique properties

  • Array of pointers to the controls attached to the tab
  • Text
  • Width

Create the TElements structure for the unique properties and declare a dynamic array with this type: 

class CTabs : public CElement
  {
private:
   //--- Structure of properties and arrays of the controls attached to each tab
   struct TElements
     {
      CElement         *elements[];
      string            m_text;
      int               m_width;
     };
   TElements         m_tab[];
  };

Common properties

  • Tabs positioning mode
  • Color of the area background
  • Size of the tabs along the Y axis (height)
  • Colors of tabs in different states
  • Color of tab texts in different states
  • Color of tab borders
  • Priorities of the left mouse button press

To set the positioning mode the ENUM_TABS_POSITION enumeration must be added to the Enums.mqh file: 

//+------------------------------------------------------------------+
//| Enumeration of the tabs positioning                              |
//+------------------------------------------------------------------+
enum ENUM_TABS_POSITION
  {
   TABS_TOP    =0,
   TABS_BOTTOM =1,
   TABS_LEFT   =2,
   TABS_RIGHT  =3
  };

Declaration of fields and methods for setting the properties are provided in the code listing below: 

class CTabs : public CElement
  {
private:
   //--- Positioning of tabs
   ENUM_TABS_POSITION m_position_mode;
   //--- Color of the common area background
   int               m_area_color;
   //--- Size of the tabs along the Y axis
   int               m_tab_y_size;
   //--- Colors of tabs in different states
   color             m_tab_color;
   color             m_tab_color_hover;
   color             m_tab_color_selected;
   color             m_tab_color_array[];
   //--- Color of tab texts in different states
   color             m_tab_text_color;
   color             m_tab_text_color_selected;
   //--- Color of tab borders
   color             m_tab_border_color;
   //--- Priorities of the left mouse button press
   int               m_zorder;
   int               m_tab_zorder;
   //---
public:
   //--- (1) Set/set the tab positions (top/bottom/left/right), (2) set the tab size along the Y axis
   void              PositionMode(const ENUM_TABS_POSITION mode)     { m_position_mode=mode;          }
   ENUM_TABS_POSITION PositionMode(void)                       const { return(m_position_mode);       }
   void              TabYSize(const int y_size)                      { m_tab_y_size=y_size;           }
   //--- Color (1) of the common background, (2) colors of tabs in different states, (3) color of tab borders
   void              AreaColor(const color clr)                      { m_area_color=clr;              }
   void              TabBackColor(const color clr)                   { m_tab_color=clr;               }
   void              TabBackColorHover(const color clr)              { m_tab_color_hover=clr;         }
   void              TabBackColorSelected(const color clr)           { m_tab_color_selected=clr;      }
   void              TabBorderColor(const color clr)                 { m_tab_border_color=clr;        }
   //--- Color of tab texts in different states
   void              TabTextColor(const color clr)                   { m_tab_text_color=clr;          }
   void              TabTextColorSelected(const color clr)           { m_tab_text_color_selected=clr; }
  };

Before creating a control, it is necessary to add the required number of tabs with an indication of the displayed text and width. Let us write the CTabs::AddTab() method for that. The default values of the method arguments are «» (empty string) and 50 (width)

class CTabs : public CElement
  {
public:
   //--- Adds a tab
   void              AddTab(const string tab_text="",const int tab_width=50);
  };
//+------------------------------------------------------------------+
//| Adds a tab                                                       |
//+------------------------------------------------------------------+
void CTabs::AddTab(const string tab_text,const int tab_width)
  {
//--- Set the size of tab arrays
   int array_size=::ArraySize(m_tabs);
   ::ArrayResize(m_tabs,array_size+1);
   ::ArrayResize(m_tab,array_size+1);
//--- Store the passed properties
   m_tab[array_size].m_text  =tab_text;
   m_tab[array_size].m_width =tab_width;
//--- Store the number of tabs
   m_tabs_total=array_size+1;
  }

If a certain tab should be preselected when loading the MQL application on the chart, then, before creating the control, it is necessary to specify its index using the CTabs::SelectedTab() method. It will also require a private CTabs::CheckTabIndex() method to check and adjust the index of the selected tab, in case it exceeds range. 

class CTabs : public CElement
  {
private:
   //--- Index of the selected tab
   int               m_selected_tab;
   //---
public:
   //--- (1) Store and (2) return index of the selected tab
   void              SelectedTab(const int index)                    { m_selected_tab=index;          }
   int               SelectedTab(void)                         const { return(m_selected_tab);        }
   //---
private:
   //--- Check index of the selected tab
   void              CheckTabIndex(void);
  };
//+------------------------------------------------------------------+
//| Check index of the selected tab                                  |
//+------------------------------------------------------------------+
void CTabs::CheckTabIndex(void)
  {
//--- Checking for exceeding the array range
   int array_size=::ArraySize(m_tab);
   if(m_selected_tab<0)
      m_selected_tab=0;
   if(m_selected_tab>=array_size)
      m_selected_tab=array_size-1;
  }

To create the control, we will need three private methods and one public method: 

class CTabs : public CElement
  {
private:
   //--- Objects for creating the element
   CRectLabel        m_main_area;
   CRectLabel        m_tabs_area;
   CEdit             m_tabs[];
   //---
public:
   //--- Methods for creating the tabs
   bool              CreateTabs(const long chart_id,const int subwin,const int x,const int y);
   //---
private:
   bool              CreateMainArea(void);
   bool              CreateTabsArea(void);
   bool              CreateButtons(void);
  };

If no tab is added before attaching the control, then calling the CTabs::CreateTabs() public method will stop creation of the graphical interface and output this message to the log: 

//--- If there is no tab in the group, report
   if(m_tabs_total<1)
     {
      ::Print(__FUNCTION__," > This method is to be called, "
              "if a group contains at least one tab! Use the CTabs::AddTab() method");
      return(false);
     }

Determination and calculation of coordinates for the component objects of the control will also be different depending on the selected tab positioning mode. These calculations require the CTabs::SumWidthTabs() method, which returns the total width of all tabs. In left (TABS_LEFT) and right (TABS_RIGHT) tab positioning modes, it will return the width of the first tab. For the top (TABS_TOP) and bottom (TABS_BOTTOM) modes, the width of all tabs is summed. 

class CTabs : public CElement
  {
private:
   //--- Positioning of tabs
   ENUM_TABS_POSITION m_position_mode;
   //---
private:
   //--- Width of all tabs
   int               SumWidthTabs(void);
  };
//+------------------------------------------------------------------+
//| Total width of all tabs                                          |
//+------------------------------------------------------------------+
int CTabs::SumWidthTabs(void)
  {
   int width=0;
//--- If tabs are positioned right or left, return the width of the first tab
   if(m_position_mode==TABS_LEFT || m_position_mode==TABS_RIGHT)
      return(m_tab[0].m_width);
//--- Sum the width of all tabs
   for(int i=0; i<m_tabs_total; i++)
      width=width+m_tab[i].m_width;
//--- With consideration of one pixel overlay
   width=width-(m_tabs_total-1);
   return(width);
  }

The CTabs::CreateMainArea() method is designed for creating the area where groups of controls will be located. Calculation of coordinates and object sizes looks like this (shortened version of the method): 

//+------------------------------------------------------------------+
//| Create the common area background                                |
//+------------------------------------------------------------------+
bool CTabs::CreateMainArea(void)
  {
//--- Forming the object name
   string name=CElement::ProgramName()+"_tabs_main_area_"+(string)CElement::Id();
//--- Coordinates
   int x=0;
   int y=0;
//--- Size
   int x_size=0;
   int y_size=0;
//--- Calculating coordinates and sizes relative to positioning of tabs
   switch(m_position_mode)
     {
      case TABS_TOP :
         x      =CElement::X();
         y      =CElement::Y()+m_tab_y_size-1;
         x_size =CElement::XSize();
         y_size =CElement::YSize()-m_tab_y_size;
         break;
      case TABS_BOTTOM :
         x      =CElement::X();
         y      =CElement::Y();
         x_size =CElement::XSize();
         y_size =CElement::YSize()-m_tab_y_size;
         break;
      case TABS_RIGHT :
         x      =CElement::X();
         y      =CElement::Y();
         x_size =CElement::XSize()-SumWidthTabs()+1;
         y_size =CElement::YSize();
         break;
      case TABS_LEFT :
         x      =CElement::X()+SumWidthTabs()-1;
         y      =CElement::Y();
         x_size =CElement::XSize()-SumWidthTabs()+1;
         y_size =CElement::YSize();
         break;
     }
//--- Creating the object
   if(!m_main_area.Create(m_chart_id,name,m_subwin,x,y,x_size,y_size))
      return(false);
//--- Setting the properties
//--- Margins from the edge
//--- Store the size
//--- Store coordinates
//--- Store the object pointer
//...
   return(true);
  }

The following is the calculation of coordinates and background sizes for the tabs depending on the positioning mode specified in the CTabs::CreateTabsArea() method: 

//+------------------------------------------------------------------+
//| Create the tab background                                        |
//+------------------------------------------------------------------+
bool CTabs::CreateTabsArea(void)
  {
//--- Forming the object name
   string name=CElement::ProgramName()+"_tabs_area_"+(string)CElement::Id();
//--- Coordinates
   int x=CElement::X();
   int y=CElement::Y();
//--- Size
   int x_size=SumWidthTabs();
   int y_size=0;
//--- Calculating sizes relative to positioning of tabs
   if(m_position_mode==TABS_TOP || m_position_mode==TABS_BOTTOM)
     {
      y_size=m_tab_y_size;
     }
   else
     {
      y_size=m_tab_y_size*m_tabs_total-(m_tabs_total-1);
     }
//--- Adjust the coordinates for positioning the tabs at the bottom and on the right
   if(m_position_mode==TABS_BOTTOM)
     {
      y=CElement::Y2()-m_tab_y_size-1;
     }
   else if(m_position_mode==TABS_RIGHT)
     {
      x=CElement::X2()-x_size;
     }
//--- Creating the object
   if(!m_tabs_area.Create(m_chart_id,name,m_subwin,x,y,x_size,y_size))
      return(false);
//--- Setting the properties
//--- Margins from the edge
//--- Store the size
//--- Store coordinates
//--- Store the object pointer
//...
   return(true);
  }

The CTabs::CreateButtons() method only requires the calculation of coordinates to create tabs. The width is set in the custom class of the application before the control is created. Otherwise, the default value (width) is used. Below is a shortened version of the method: 

//+------------------------------------------------------------------+
//| Create tabs                                                      |
//+------------------------------------------------------------------+
bool CTabs::CreateButtons(void)
  {
//--- Coordinates
   int x =CElement::X();
   int y =CElement::Y();
//--- Calculating coordinates relative to positioning of tabs
   if(m_position_mode==TABS_BOTTOM)
      y=CElement::Y2()-m_tab_y_size-1;
   else if(m_position_mode==TABS_RIGHT)
      x=CElement::X2()-SumWidthTabs();
//--- Check index of the selected tab
   CheckTabIndex();
//--- Create tabs
   for(int i=0; i<m_tabs_total; i++)
     {
      //--- Forming the object name
      string name=CElement::ProgramName()+"_tabs_edit_"+(string)i+"__"+(string)CElement::Id();
      //--- Calculating coordinates relative to positioning of tabs for each individual tab
      if(m_position_mode==TABS_TOP || m_position_mode==TABS_BOTTOM)
         x=(i>0) ? x+m_tab[i-1].m_width-1 : CElement::X();
      else
         y=(i>0) ? y+m_tab_y_size-1 : CElement::Y();
      //--- Creating the object
      if(!m_tabs[i].Create(m_chart_id,name,m_subwin,x,y,m_tab[i].m_width,m_tab_y_size))
         return(false);
      //--- Setting the properties
      //--- Margins from the edge of the panel
      //--- Coordinates
      //--- Size
      //--- Initializing gradient array
      //--- Store the object pointer
     }
//---
   return(true);
  }

To attach any control to a specific tab, let us write the CTabs::AddToElementsArray() method. It has two arguments: (1) tab index, to which the control should be attached and (2) reference to the control, a pointer to which should be stored in the array of tab controls

class CTabs : public CElement
  {
public:
   //--- Add control to the tab array
   void              AddToElementsArray(const int tab_index,CElement &object);
  };
//+------------------------------------------------------------------+
//| Add control to the array of the specified tab                    |
//+------------------------------------------------------------------+
void CTabs::AddToElementsArray(const int tab_index,CElement &object)
  {
//--- Checking for exceeding the array range
   int array_size=::ArraySize(m_tab);
   if(array_size<1 || tab_index<0 || tab_index>=array_size)
      return;
//--- Add pointer of the passed control to array of the specified tab
   int size=::ArraySize(m_tab[tab_index].elements);
   ::ArrayResize(m_tab[tab_index].elements,size+1);
   m_tab[tab_index].elements[size]=::GetPointer(object);
  }

When switching tabs, it is necessary to hide the controls of the previous tab and display the controls of the newly selected tab. For this purpose, let us create the CTabs::ShowTabElements() method. A check for the control visibility is at the beginning of the method. If the control is hidden, then the program leaves the method. Next, it checks the index of the active tab and adjusts it, if necessary. Then it checks all tabs in a loop and performs the main task of the method.  

class CTabs : public CElement
  {
public:
   //--- Show controls of the selected tab only
   void              ShowTabElements(void);
  };
//+------------------------------------------------------------------+
//| Show controls of the selected tab only                           |
//+------------------------------------------------------------------+
void CTabs::ShowTabElements(void)
  {
//--- Leave, if the tabs are hidden
   if(!CElement::IsVisible())
      return;
//--- Check index of the selected tab
   CheckTabIndex();
//---
   for(int i=0; i<m_tabs_total; i++)
     {
      //--- Get the number of controls attached to the tab
      int tab_elements_total=::ArraySize(m_tab[i].elements);
      //--- If this tab is selected
      if(i==m_selected_tab)
        {
         //--- Display the tab controls
         for(int j=0; j<tab_elements_total; j++)
            m_tab[i].elements[j].Show();
        }
      //--- Hide the controls of inactive tabs
      else
        {
         for(int j=0; j<tab_elements_total; j++)
            m_tab[i].elements[j].Hide();
        }
     }
  }

The CTabs::OnClickTab() method will be used to handle the pressing of a tab. First, the program has to pass two checks: (1) based on the name of the pressed object and (2) based on the identifier of the control, retrieved from the object's name using the CTabs::IdFromObjectName() method. If the checks are passed, then the program (1) finds the pressed tab in a loop, (2) stores its index and (3) sets the corresponding colors. At the very end of the CTabs::ShowTabElements() method, the controls of only the active tab are made visible. 

class CTabs : public CElement
  {
private:
   //--- Handling the pressing on tab
   bool              OnClickTab(const string pressed_object);
   //--- Get the identifier from the object name
   int               IdFromObjectName(const string object_name);
  };
//+------------------------------------------------------------------+
//| Pressing a tab in a group                                        |
//+------------------------------------------------------------------+
bool CTabs::OnClickTab(const string clicked_object)
  {
//--- Leave, if the pressing was not on the table cell
   if(::StringFind(clicked_object,CElement::ProgramName()+"_tabs_edit_",0)<0)
      return(false);
//--- Get the identifier from the object name
   int id=IdFromObjectName(clicked_object);
//--- Leave, if the identifier does not match
   if(id!=CElement::Id())
      return(false);
//---
   for(int i=0; i<m_tabs_total; i++)
     {
      //--- If this tab is clicked
      if(m_tabs[i].Name()==clicked_object)
        {
         //--- Store index of the selected tab
         SelectedTab(i);
         //--- Set colors
         m_tabs[i].Color(m_tab_text_color_selected);
         m_tabs[i].BackColor(m_tab_color_selected);
        }
      else
        {
         //--- Set the colors for the inactive tabs
         m_tabs[i].Color(m_tab_text_color);
         m_tabs[i].BackColor(m_tab_color);
        }
     }
//--- Show controls of the selected tab only
   ShowTabElements();
   return(true);
  }

In this case, the code for the CTabs::OnEvent() main event handler is as shown in the listing below: 

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void CTabs::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handling of the cursor movement event
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Leave, if the element is hidden
      if(!CElement::IsVisible())
         return;
      //--- Coordinates
      int x=(int)lparam;
      int y=(int)dparam;
      for(int i=0; i<m_tabs_total; i++)
         m_tabs[i].MouseFocus(x>m_tabs[i].X() && x<m_tabs[i].X2() && y>m_tabs[i].Y() && y<m_tabs[i].Y2());
      //---
      return;
     }
//--- Handling the left mouse button click on the object
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Pressing a tab
      if(OnClickTab(sparam))
         return;
     }
  }

All the methods of the CTabs class have been considered. Now, let us test how all this works. 

 


Testing the Tabs Control

For testing, let us use the EA from the previous part of the series and remove everything except the main menu and status bar from its graphical interface. Let us make all tab positioning modes (top/bottom/right/left) easy to test, as well as their size (height) easy to adjust. For this purpose, add external parameters to the EA in the Program.mqh file with the custom class:

//--- External parameters of the Expert Advisor
input ENUM_TABS_POSITION TabsPosition =TABS_TOP; // Tabs Position
input                int TabsHeight   =20;       // Tabs Height

Next, in the custom class (CProgram) of the application, declare an instance of the CTabs class and method for creating a Tabs control indented from the edge points of the form: 

class CProgram : public CWndEvents
  {
private:
   //--- Tabs
   CTabs             m_tabs;
   //---
private:
   //--- Tabs
#define TABS1_GAP_X           (4)
#define TABS1_GAP_Y           (45)
   bool              CreateTabs(void);
  };

There will be four tabs in total. The displayed text and width of tabs can be adjusted by initializing arrays, the values of their elements are then passed in a loop using the CTabs::AddTab() method. Height and positioning of the tabs will be set by the external parameters. The second tab (index 1) will be selected by default (when the program is loaded on chart for the first time). The full code of the CProgram::CreateTabs() method can be seen in the listing below: 

//+------------------------------------------------------------------+
//| Create area with tabs                                            |
//+------------------------------------------------------------------+
bool CProgram::CreateTabs(void)
  {
#define TABS1_TOTAL 4
//--- Pass the panel object
   m_tabs.WindowPointer(m_window1);
//--- Coordinates
   int x=m_window1.X()+TABS1_GAP_X;
   int y=m_window1.Y()+TABS1_GAP_Y;
//--- Arrays with text and width for tabs
   string tabs_text[]={"Tab 1","Tab 2","Tab 3","Tab 4"};
   int tabs_width[]={90,90,90,90};
//--- Set properties before creation
   m_tabs.XSize(596);
   m_tabs.YSize(243);
   m_tabs.TabYSize(TabsHeight);
   m_tabs.PositionMode(TabsPosition);
   m_tabs.SelectedTab((m_tabs.SelectedTab()==WRONG_VALUE) ? 1 : m_tabs.SelectedTab());
   m_tabs.AreaColor(clrWhite);
   m_tabs.TabBackColor(C'225,225,225');
   m_tabs.TabBackColorHover(C'240,240,240');
   m_tabs.TabBackColorSelected(clrWhite);
   m_tabs.TabBorderColor(clrSilver);
   m_tabs.TabTextColor(clrGray);
   m_tabs.TabTextColorSelected(clrBlack);
//--- Add tabs with the specified properties
   for(int i=0; i<TABS1_TOTAL; i++)
      m_tabs.AddTab(tabs_text[i],tabs_width[i]);
//--- Create control
   if(!m_tabs.CreateTabs(m_chart_id,m_subwin,x,y))
      return(false);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(0,m_tabs);
   return(true);
  }

The method must be called in the main method for creating the graphical interface of the application (see the shortened version of the method in listing below): 

//+------------------------------------------------------------------+
//| Create an expert panel                                           |
//+------------------------------------------------------------------+
bool CProgram::CreateExpertPanel(void)
  {
//--- Creating form 1 for controls
//--- Creating controls:
//    Main menu
//--- Context menus
//--- Tabs
   if(!CreateTabs())
      return(false);
//--- Redrawing the chart
   m_chart.Redraw();
   return(true);
  }

Compile the program and load it on to the chart. The tab positioning modes will be changed successively in the external parameters (see screenshots below):

 Fig. 2. Tabs positioning mode — «Top».

Fig. 2. Tabs positioning mode — «Top».

 Fig. 3. Tabs positioning mode — «Bottom».

Fig. 3. Tabs positioning mode — «Bottom».

 Fig. 4. Tabs positioning mode — «Left».

Fig. 4. Tabs positioning mode — «Left».

 Fig. 5. Tabs positioning mode — «Right».

Fig. 5. Tabs positioning mode — «Right».


Now let us test how it works with groups of controls attached to each tab. To do that, create a separate copy of the same EA and delete the external parameters from it. Here, the tabs will be positioned at the top (TABS_TOP) of the workspace. 

  1. A text label table will be attached to the first tab. 
  2. An edit box table will be attached to the second. 
  3. A rendered table to the third.
  4. The fourth — a group of controls, containing:

    • four checkboxes;
    • four checkboxes with edit boxes;
    • four comboboxes with checkboxes;
    • one separation line. 

In the custom class (CProgram) of the test application, declare (1) instances of these controls, (2) methods for creating them and (3) margins from the edges of the form (see code listing below): 

class CProgram : public CWndEvents
  {
private:
   //--- Text label table
   CLabelsTable      m_labels_table;
   //--- Edit box table
   CTable            m_table;
   //--- Rendered table
   CCanvasTable      m_canvas_table;
   //--- Checkboxes
   CCheckBox         m_checkbox1;
   CCheckBox         m_checkbox2;
   CCheckBox         m_checkbox3;
   CCheckBox         m_checkbox4;
   //--- Checkboxes with edit boxes
   CCheckBoxEdit     m_checkboxedit1;
   CCheckBoxEdit     m_checkboxedit2;
   CCheckBoxEdit     m_checkboxedit3;
   CCheckBoxEdit     m_checkboxedit4;
   //--- Comboboxes with checkboxes
   CCheckComboBox    m_checkcombobox1;
   CCheckComboBox    m_checkcombobox2;
   CCheckComboBox    m_checkcombobox3;
   CCheckComboBox    m_checkcombobox4;
   //--- Separation line
   CSeparateLine     m_sep_line;
   //---
private:
   //--- Text label table
#define TABLE1_GAP_X          (5)
#define TABLE1_GAP_Y          (65)
   bool              CreateLabelsTable(void);
   //--- Edit box table
#define TABLE2_GAP_X          (5)
#define TABLE2_GAP_Y          (65)
   bool              CreateTable(void);
   //--- Rendered table
#define TABLE3_GAP_X          (5)
#define TABLE3_GAP_Y          (65)
   bool              CreateCanvasTable(void);
   //--- Separation line
#define SEP_LINE_GAP_X        (300)
#define SEP_LINE_GAP_Y        (70)
   bool              CreateSepLine(void);
   //--- Checkboxes
#define CHECKBOX1_GAP_X       (18)
#define CHECKBOX1_GAP_Y       (75)
   bool              CreateCheckBox1(const string text);
#define CHECKBOX2_GAP_X       (18)
#define CHECKBOX2_GAP_Y       (175)
   bool              CreateCheckBox2(const string text);
#define CHECKBOX3_GAP_X       (315)
#define CHECKBOX3_GAP_Y       (75)
   bool              CreateCheckBox3(const string text);
#define CHECKBOX4_GAP_X       (315)
#define CHECKBOX4_GAP_Y       (175)
   bool              CreateCheckBox4(const string text);
   //--- Checkboxes with edit boxes
#define CHECKBOXEDIT1_GAP_X   (40)
#define CHECKBOXEDIT1_GAP_Y   (105)
   bool              CreateCheckBoxEdit1(const string text);
#define CHECKBOXEDIT2_GAP_X   (40)
#define CHECKBOXEDIT2_GAP_Y   (135)
   bool              CreateCheckBoxEdit2(const string text);
#define CHECKBOXEDIT3_GAP_X   (337)
#define CHECKBOXEDIT3_GAP_Y   (105)
   bool              CreateCheckBoxEdit3(const string text);
#define CHECKBOXEDIT4_GAP_X   (337)
#define CHECKBOXEDIT4_GAP_Y   (135)
   bool              CreateCheckBoxEdit4(const string text);
   //--- Comboboxes with checkboxes
#define CHECKCOMBOBOX1_GAP_X  (40)
#define CHECKCOMBOBOX1_GAP_Y  (205)
   bool              CreateCheckComboBox1(const string text);
#define CHECKCOMBOBOX2_GAP_X  (40)
#define CHECKCOMBOBOX2_GAP_Y  (235)
   bool              CreateCheckComboBox2(const string text);
#define CHECKCOMBOBOX3_GAP_X  (337)
#define CHECKCOMBOBOX3_GAP_Y  (205)
   bool              CreateCheckComboBox3(const string text);
#define CHECKCOMBOBOX4_GAP_X  (337)
#define CHECKCOMBOBOX4_GAP_Y  (235)
   bool              CreateCheckComboBox4(const string text);
  };

In the previous articles, it had been repeatedly shown how to create controls and attach them to the form. Therefore, the code of only one of these methods will be provided here to show how to attach a control to a tab. The simplest of those controls - separation line - would suffice as an example. In the code listing below, the line that call to the CTabs::AddToElementsArray() method is highlighted in yellow. The first argument is index of the tab, to which a control should be attached. Here the index is 3, that is, the fourth tab. The second argument is the object of the control that must be attached to the specified tab.

//+------------------------------------------------------------------+
//| Creates a separation line                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateSepLine(void)
  {
//--- Store the window pointer
   m_sep_line.WindowPointer(m_window1);
//--- Attach to the fourth tab of the first group of tabs
   m_tabs.AddToElementsArray(3,m_sep_line);
//--- Coordinates  
   int x=m_window1.X()+SEP_LINE_GAP_X;
   int y=m_window1.Y()+SEP_LINE_GAP_Y;
//--- Size
   int x_size=2;
   int y_size=210;
//--- Set properties before creation
   m_sep_line.DarkColor(C'213,223,229');
   m_sep_line.LightColor(clrWhite);
   m_sep_line.TypeSepLine(V_SEP_LINE);
//--- Creating an element
   if(!m_sep_line.CreateSeparateLine(m_chart_id,m_subwin,0,x,y,x_size,y_size))
      return(false);
//--- Add the element pointer to the base
   CWndContainer::AddToElementsArray(0,m_sep_line);
   return(true);
  }

Once the graphical interface of the application has been created, the CTabs::ShowTabElements() method must be called in order to display the controls of the active tabs only (see the shortened version of the method in listing below). If it is not done, then all controls of all tabs will be displayed. 

//+------------------------------------------------------------------+
//| Create an expert panel                                           |
//+------------------------------------------------------------------+
bool CProgram::CreateExpertPanel(void)
  {
//--- Creating form 1 for controls
//--- Creating controls:
//--- Main menu
//--- Context menus
//--- Status bar
//--- Tabs
//...
//--- Text label table
   if(!CreateLabelsTable())
      return(false);
//--- Edit box table
   if(!CreateTable())
      return(false);
//--- Create rendered table
   if(!CreateCanvasTable())
      return(false);
//--- Separation line
   if(!CreateSepLine())
      return(false);
//--- Checkboxes
   if(!CreateCheckBox1("Checkbox 1"))
      return(false);
   if(!CreateCheckBox2("Checkbox 2"))
      return(false);
   if(!CreateCheckBox3("Checkbox 3"))
      return(false);
   if(!CreateCheckBox4("Checkbox 4"))
      return(false);
//--- Checkboxes with edit boxes
   if(!CreateCheckBoxEdit1("Checkbox Edit 1:"))
      return(false);
   if(!CreateCheckBoxEdit2("Checkbox Edit 2:"))
      return(false);
   if(!CreateCheckBoxEdit3("Checkbox Edit 3:"))
      return(false);
   if(!CreateCheckBoxEdit4("Checkbox Edit 4:"))
      return(false);
//--- Comboboxes with checkboxes
   if(!CreateCheckComboBox1("CheckCombobox 1:"))
      return(false);
   if(!CreateCheckComboBox2("CheckCombobox 2:"))
      return(false);
   if(!CreateCheckComboBox3("CheckCombobox 3:"))
      return(false);
   if(!CreateCheckComboBox4("CheckCombobox 4:"))
      return(false);
//--- Display controls of the active tab only
   m_tabs.ShowTabElements();
//--- Redrawing the chart
   m_chart.Redraw();
   return(true);
  }

The result should be as in the screenshots below

 Fig. 6. Controls of the fist tab.

Fig. 6. Controls of the fist tab.

Fig. 7. Controls of the second tab. 

Fig. 7. Controls of the second tab.

 Fig. 8. Controls of the third tab.

Fig. 8. Controls of the third tab.

 Fig. 9. Controls of the fourth tab.

Fig. 9. Controls of the fourth tab.


Everything works as intended. The seventh part of the series dedicated to the library for creating graphical interfaces can be now concluded. As a supplement, you can download another class code (CIconTabs) for creating Tabs with extended functionality, which is provided in the article attachments. Unlike the CTabs class, a control of CIconTabs type can have adjustable icons for each tab. This can help to make the graphical interface more user-friendly, if necessary. 

The icons and displayed text can be accurately placed relative to the edge of each tab using special methods (see code listing below): 

//+------------------------------------------------------------------+
//| Class for creating icon tabs                                     |
//+------------------------------------------------------------------+
class CIconTabs : public CElement
  {
private:
   //--- Label margins
   int               m_icon_x_gap;
   int               m_icon_y_gap;
   //--- Text label margins
   int               m_label_x_gap;
   int               m_label_y_gap;
   //---
public:
   //--- Label margins
   void              IconXGap(const int x_gap)                       { m_icon_x_gap=x_gap;            }
   void              IconYGap(const int y_gap)                       { m_icon_y_gap=y_gap;            }
   //--- Text label margins
   void              LabelXGap(const int x_gap)                      { m_label_x_gap=x_gap;           }
   void              LabelYGap(const int y_gap)                      { m_label_y_gap=y_gap;           }
  };

An example of the Icon tabs control appearance is displayed in the screenshot below:

 Fig. 10. «Icon tabs» control.

Fig. 10. Icon tabs control.

The code of this expert can also be downloaded from the article attachments. 

 

 


Conclusion

Currently, the schematic of the library for creating graphical interfaces looks as shown below:

 Fig. 11. Structure of the library at the current stage of development.

Fig. 11. Structure of the library at the current stage of development.

The tables and tabs controls have been covered in the seventh part of the series about creating graphical interfaces in the MetaTrader trading terminals. Three classes (CLabelsTable, CTable and CCanvasTable) have been provided for creating tables and two classes (CTabs and CIconTabs) for creating tabs. 

The next (eighth) part of the series will consider the following controls.

  • Static and drop-down calendar.
  • Tree view.
  • File navigator.

You can find and download the material of the seventh part of the series in the attached files so you can test how it works. If you have questions on how to use the material, presented in these 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 below.

List of articles (chapters) of seventh part:

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

Attached files |
Last comments | Go to discussion (1)
Martin Fischer
Martin Fischer | 25 Aug 2016 at 14:52
Hi. A really nice article, but at the moment I've some questions:

1.)
I do not find an information, how I can add simple CLabel or CEdit controls to the form. (From objects.mqh)
They do not have a function like WindowPointer().
I can create these objects, but I can't attach them to the window.

The
CWndContainer::AddToElementsArray(m_subwin, _MyEditBox);
does not work in this case...

2.)
Is the CComboBox a static object. Is it possible to modify the list of elements in the

ComboBox-Listview after the creation of the ComboBox?

Thank you!

Step on New Rails: Custom Indicators in MQL5 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 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.

False trigger protection for Trading Robot False trigger protection for Trading Robot

Profitability of trading systems is defined not only by logic and precision of analyzing the financial instrument dynamics, but also by the quality of the performance algorithm of this logic. False trigger is typical for low quality performance of the main logic of a trading robot. Ways of solving the specified problem are considered in this article.

Using text files for storing input parameters of Expert Advisors, indicators and scripts 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.