Русский 中文 Español Deutsch 日本語 Português
Graphical Interfaces VII: the Tables Controls (Chapter 1)

Graphical Interfaces VII: the Tables Controls (Chapter 1)

MetaTrader 5Examples | 3 August 2016, 10:43
10 995 4
Anatoli Kazharski
Anatoli Kazharski

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.

This article deals with three classes that allow you to create different types of tables to display two-dimensional data sets as part of MQL application GUI:

  • text label table;
  • edit box table;
  • rendered table.

Each table type has its own unique features and advantages described below.

In the next article (part VII, chapter 2), we will describe the following controls:

  • tabs;
  • tabs with images.

 


The Text Label Table Control

A table is a complex GUI control since it includes other controls – horizontal and vertical scroll bars. Since data may exceed available space of the control's specified area, scroll bars allow you to shift displayed data arrays both vertically and horizontally.

Text label tables consist of the following components:

  1. Background.
  2. Text labels.
  3. Vertical scroll bar.
  4. Horizontal scroll bar.

 Fig. 1. Compound parts of the text label control

Fig. 1. Compound parts of the text label table control

Let us take a closer look at the class of this control.

 


Developing CLabelsTable Class

Create LabelsTable.mqh file and include it in the library (WndContainer.mqh):

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

In LabelsTable.mqh file, create CLabelsTable class with the standard set of methods that should be present in every control of the library, as well as the method for saving the pointer to the form the control should be connected to. Do the same to all controls mentioned in the current article.

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

Before creating the table, specify some of its properties. Let's name them.

  • Row height
  • First column indent from the left edge of the control
  • Distance between columns
  • Background color
  • Text color
  • First row lock mode
  • First column lock mode
  • Total number of columns
  • Total number of rows
  • Number of columns of the table's visible part
  • Number of rows of the table's visible part

In this table type, text label coordinates are calculated from the central anchor point (ANCHOR_CENTER). Therefore, the indent from the control's left edge for text labels of the first column, as well as the distance between text labels of each column are calculated from the center of each of them.

As for the lock modes, it may be necessary for the table's first row and/or first column (where a user can set names for each column and/or data series) to remain visible at all times even when the sliders of the vertical and/or horizontal scroll bars are moved away from the first position. Header lock modes can be used both together and separately. 

class CLabelsTable : public CElement
  {
private:
   //--- Row height
   int               m_row_y_size;
   //--- Table background color
   color             m_area_color;
   //--- Default color of table text
   color             m_text_color;
   //--- Distance between the anchor point of the first column and the left edge of the control
   int               m_x_offset;
   //--- Distance between the anchor points of the columns
   int               m_column_x_offset;
   //--- Lock (fixation) mode of the first row
   bool              m_fix_first_row;
   //--- Fixation mode of the first column
   bool              m_fix_first_column; 
   //--- Priorities of the left mouse button press
   int               m_zorder;
   int               m_area_zorder;
   //---
public:
   //--- (1) Background color, (2) text color
   void              AreaColor(const color clr)                   { m_area_color=clr;                }
   void              TextColor(const color clr)                   { m_text_color=clr;                }
   //--- (1) Row height, (2) set the distance between the anchor point of the first column and the left edge of the table,
   //    (3) set the distance between anchor points of columns
   void              RowYSize(const int y_size)                   { m_row_y_size=y_size;             }
   void              XOffset(const int x_offset)                  { m_x_offset=x_offset;             }
   void              ColumnXOffset(const int x_offset)            { m_column_x_offset=x_offset;      }
   //--- (1) Get and (2) set the fixation mode of the first row
   bool              FixFirstRow(void)                      const { return(m_fix_first_row);         }
   void              FixFirstRow(const bool flag)                 { m_fix_first_row=flag;            }
   //--- (1) Get and (2) set the fixation mode of the first column
   bool              FixFirstColumn(void)                   const { return(m_fix_first_column);      }
   void              FixFirstColumn(const bool flag)              { m_fix_first_column=flag;         }
  };

Let's create the CLabelsTable::TableSize() and CLabelsTable::VisibleTableSize() methods to set a total and visible number of the table columns and rows. Besides, we need two-dimensional dynamic arrays in the form of structures. One of the structures (LTLabels) creates the array of text labels for the table's visible part, while another (LTOptions) stores all values and properties of each table cell. In our implementation, it will store displayed values (rows) and text color.

Two arguments (numbers of columns and rows) should be passed to the CLabelsTable::TableSize() and CLabelsTable::VisibleTableSize() methods. The passed values undergo correction at the beginning of the methods in case the number of columns is less than one, while the number of rows is less than two. Sizes of all arrays are defined and property arrays are initialized afterwards.

Apart from table size methods, we also need methods for receiving its total and visible sizes for columns and rows. 

class CLabelsTable : public CElement
  {
private:
   //--- Array of objects for the visible part of the table
   struct LTLabels
     {
      CLabel            m_rows[];
     };
   LTLabels          m_columns[];
   //--- Array of table values and properties
   struct LTOptions
     {
      string            m_vrows[];
      color             m_colors[];
     };
   LTOptions         m_vcolumns[];
   //---
public:
   //--- Returns the total number of (1) rows and (2) columns
   int               RowsTotal(void)                        const { return(m_rows_total);            }
   int               ColumnsTotal(void)                     const { return(m_columns_total);         }
   //--- Returns the number of (1) rows and (2) columns of the visible part of the table
   int               VisibleRowsTotal(void)                 const { return(m_visible_rows_total);    }
   int               VisibleColumnsTotal(void)              const { return(m_visible_columns_total); }

   //--- Set the (1) size of the table and (2) size of its visible part
   void              TableSize(const int columns_total,const int rows_total);
   void              VisibleTableSize(const int visible_columns_total,const int visible_rows_total);
  };
//+------------------------------------------------------------------+
//| Set the size of the table                                        |
//+------------------------------------------------------------------+
void CLabelsTable::TableSize(const int columns_total,const int rows_total)
  {
//--- There must be at least one column
   m_columns_total=(columns_total<1) ? 1 : columns_total;
//--- There must be at least two rows
   m_rows_total=(rows_total<2) ? 2 : rows_total;
//--- Set the size of the columns array
   ::ArrayResize(m_vcolumns,m_columns_total);
//--- Set the size of the rows arrays
   for(int i=0; i<m_columns_total; i++)
     {
      ::ArrayResize(m_vcolumns[i].m_vrows,m_rows_total);
      ::ArrayResize(m_vcolumns[i].m_colors,m_rows_total);
      //--- Initialize the array of text colors with the default value
      ::ArrayInitialize(m_vcolumns[i].m_colors,m_text_color);
     }
  }
//+------------------------------------------------------------------+
//| Set the size of the visible part of the table                    |
//+------------------------------------------------------------------+
void CLabelsTable::VisibleTableSize(const int visible_columns_total,const int visible_rows_total)
  {
//--- There must be at least one column
   m_visible_columns_total=(visible_columns_total<1) ? 1 : visible_columns_total;
//--- There must be at least two rows
   m_visible_rows_total=(visible_rows_total<2) ? 2 : visible_rows_total;
//--- Set the size of the columns array
   ::ArrayResize(m_columns,m_visible_columns_total);
//--- Set the size of the rows arrays
   for(int i=0; i<m_visible_columns_total; i++)
      ::ArrayResize(m_columns[i].m_rows,m_visible_rows_total);
  }

All properties should be initialized by default values before creating the control. I recommend doing this right in the class constructor:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CLabelsTable::CLabelsTable(void) : m_fix_first_row(false),
                                   m_fix_first_column(false),
                                   m_row_y_size(18),
                                   m_x_offset(30),
                                   m_column_x_offset(60),
                                   m_area_color(clrWhiteSmoke),
                                   m_text_color(clrBlack),
                                   m_rows_total(2),
                                   m_columns_total(1),
                                   m_visible_rows_total(2),
                                   m_visible_columns_total(1)
  {
//--- 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;
   m_area_zorder =1;
//--- Set the size of the table and its visible part
   TableSize(m_columns_total,m_rows_total);
   VisibleTableSize(m_visible_columns_total,m_visible_rows_total);
  }

Let's create four private and one public method for an external call to develop the control. In order to allow users to configure the table's scroll bars, we should add the methods that return their pointers. 

class CLabelsTable : public CElement
  {
private:
   //--- Objects for creating a table
   CRectLabel        m_area;
   CScrollV          m_scrollv;
   CScrollH          m_scrollh;
   //--- Array of objects for the visible part of the table
   struct LTLabels
     {
      CLabel            m_rows[];
     };
   LTLabels          m_columns[];
   //---
public:
   //--- Return pointers to scroll bars
   CScrollV         *GetScrollVPointer(void)                const { return(::GetPointer(m_scrollv)); }
   CScrollH         *GetScrollHPointer(void)                const { return(::GetPointer(m_scrollh)); }
   //--- Methods for creating table
   bool              CreateLabelsTable(const long chart_id,const int subwin,const int x,const int y);
   //---
private:
   bool              CreateArea(void);
   bool              CreateLabels(void);
   bool              CreateScrollV(void);
   bool              CreateScrollH(void);
  };

Of all the object creation methods, only CLabelsTable::CreateLabels() for developing the text label array is shown here. Make sure to add column and row indices when forming an object name. All other methods can be found in the files attached below. 

//+------------------------------------------------------------------+
//| Create an array of text labels                                   |
//+------------------------------------------------------------------+
bool CLabelsTable::CreateLabels(void)
  {
//--- Coordinates and offset
   int x      =CElement::X();
   int y      =0;
   int offset =0;
//--- Columns
   for(int c=0; c<m_visible_columns_total; c++)
     {
      //--- Calculation of the table offset
      offset=(c>0) ? m_column_x_offset : m_x_offset;
      //--- Calculation of the X coordinate
      x=x+offset;
      //--- Rows
      for(int r=0; r<m_visible_rows_total; r++)
        {
         //--- Form the object name
         string name=CElement::ProgramName()+"_labelstable_label_"+(string)c+"_"+(string)r+"__"+(string)CElement::Id();
         //--- Calculate the Y coordinate
         y=(r>0) ? y+m_row_y_size-1 : CElement::Y()+10;
         //--- Create the object
         if(!m_columns[c].m_rows[r].Create(m_chart_id,name,m_subwin,x,y))
            return(false);
         //--- Setting the properties
         m_columns[c].m_rows[r].Description(m_vcolumns[c].m_vrows[r]);
         m_columns[c].m_rows[r].Font(FONT);
         m_columns[c].m_rows[r].FontSize(FONT_SIZE);
         m_columns[c].m_rows[r].Color(m_text_color);
         m_columns[c].m_rows[r].Corner(m_corner);
         m_columns[c].m_rows[r].Anchor(ANCHOR_CENTER);
         m_columns[c].m_rows[r].Selectable(false);
         m_columns[c].m_rows[r].Z_Order(m_zorder);
         m_columns[c].m_rows[r].Tooltip("\n");
         //--- Margins from the edge of the form
         m_columns[c].m_rows[r].XGap(x-m_wnd.X());
         m_columns[c].m_rows[r].YGap(y-m_wnd.Y());
         //--- Coordinates
         m_columns[c].m_rows[r].X(x);
         m_columns[c].m_rows[r].Y(y);
         //--- Store the object pointer
         CElement::AddToArray(m_columns[c].m_rows[r]);
        }
     }
//---
   return(true);
  }

We will need methods for changing values and properties in any table cell at any time after the table is created. Let's write the CLabelsTable::SetValue() and CLabelsTable::GetValue() methods to change and receive a cell value. For the both methods, the indices of the table column and row should be passed as the first two arguments. The value to be placed into the array for the specified indices should be passed as a third argument in the CLabelsTable::SetValue() method. Check for exceeding the array range is obligatory at the beginning of the methods. 

class CLabelsTable : public CElement
  {
public:
   //--- Set the value to the specified table cell
   void              SetValue(const int column_index,const int row_index,const string value);
   //--- Get the value from the specified table cell
   string            GetValue(const int column_index,const int row_index);
  };
//+------------------------------------------------------------------+
//| Set the value at the specified indices                           |
//+------------------------------------------------------------------+
void CLabelsTable::SetValue(const int column_index,const int row_index,const string value)
  {
//--- Checking for exceeding the column range
   int csize=::ArraySize(m_vcolumns);
   if(csize<1 || column_index<0 || column_index>=csize)
      return;
//--- Checking for exceeding the row range
   int rsize=::ArraySize(m_vcolumns[column_index].m_vrows);
   if(rsize<1 || row_index<0 || row_index>=rsize)
      return;
//--- Set the value
   m_vcolumns[column_index].m_vrows[row_index]=value;
  }
//+------------------------------------------------------------------+
//| Return value at the specified index                              |
//+------------------------------------------------------------------+
string CLabelsTable::GetValue(const int column_index,const int row_index)
  {
//--- Checking for exceeding the column range
   int csize=::ArraySize(m_vcolumns);
   if(csize<1 || column_index<0 || column_index>=csize)
      return("");
//--- Checking for exceeding the row range
   int rsize=::ArraySize(m_vcolumns[column_index].m_vrows);
   if(rsize<1 || row_index<0 || row_index>=rsize)
      return("");
//--- Return the value
   return(m_vcolumns[column_index].m_vrows[row_index]);
  }

Apart from changing the table values, library users may want to change the text color. For example, positive values can be displayed in green, while negative ones can be displayed in red. Let's implement the CLabelsTable::TextColor() method for that. It is similar to CLabelsTable::SetValue() with the only difference being that a color is passed as the third argument. 

class CLabelsTable : public CElement
  {
public:
   //--- Change the text color in the specified table cell
   void              TextColor(const int column_index,const int row_index,const color clr);
  };
//+------------------------------------------------------------------+
//| Change color at the specified indices                            |
//+------------------------------------------------------------------+
void CLabelsTable::TextColor(const int column_index,const int row_index,const color clr)
  {
//--- Checking for exceeding the column range
   int csize=::ArraySize(m_vcolumns);
   if(csize<1 || column_index<0 || column_index>=csize)
      return;
//--- Checking for exceeding the row range
   int rsize=::ArraySize(m_vcolumns[column_index].m_vrows);
   if(rsize<1 || row_index<0 || row_index>=rsize)
      return;
//--- Set the color
   m_vcolumns[column_index].m_colors[row_index]=clr;
  }

Implemented changes are displayed only after the table is updated. To do this, we should create a universal method that is also to be used to shift the table data relative to the position of the scroll bar sliders. 

Let's call this method CLabelsTable::UpdateTable(). If headers are locked, the data shift starts from the second (1) array index to ensure that the first row is always on the top and/or extreme (left) column is always on the left. t and l variables are declared at the beginning of the method, and the value of 1 or 0 is assigned to them depending on the currently used mode.

In order to define the index, from which the data shift/update is to start, the current positions of the scroll bar sliders should be obtained. Headers in the left column and the upper row are shifted in separate loops (if the modes are enabled).

The table's basic data and cell color are shifted in the double loop at the end of the method. 

class CLabelsTable : public CElement
  {
public:
   //--- Update table data with consideration of the recent changes
   void              UpdateTable(void);
  };
//+------------------------------------------------------------------+
//| Update table data with consideration of the recent changes       |
//+------------------------------------------------------------------+
void CLabelsTable::UpdateTable(void)
  {
//--- Shift by one index, if the fixed header mode is enabled
   int t=(m_fix_first_row) ? 1 : 0;
   int l=(m_fix_first_column) ? 1 : 0;
//--- Get the current positions of sliders of the vertical and horizontal scroll bars
   int h=m_scrollh.CurrentPos()+l;
   int v=m_scrollv.CurrentPos()+t;
//--- Shift of the headers in the left column
   if(m_fix_first_column)
     {
      m_columns[0].m_rows[0].Description(m_vcolumns[0].m_vrows[0]);
      //--- Rows
      for(int r=t; r<m_visible_rows_total; r++)
        {
         if(r>=t && r<m_rows_total)
            m_columns[0].m_rows[r].Description(m_vcolumns[0].m_vrows[v]);
         //---
         v++;
        }
     }
//--- Shift of the headers in the top row
   if(m_fix_first_row)
     {
      m_columns[0].m_rows[0].Description(m_vcolumns[0].m_vrows[0]);
      //--- Columns
      for(int c=l; c<m_visible_columns_total; c++)
        {
         if(h>=l && h<m_columns_total)
            m_columns[c].m_rows[0].Description(m_vcolumns[h].m_vrows[0]);
         //---
         h++;
        }
     }
//--- Get the current position of slider of the horizontal scroll bar
   h=m_scrollh.CurrentPos()+l;
//--- Columns
   for(int c=l; c<m_visible_columns_total; c++)
     {
      //--- Get the current position of slider of the vertical scroll bar
      v=m_scrollv.CurrentPos()+t;
      //--- Rows
      for(int r=t; r<m_visible_rows_total; r++)
        {
         //--- Shift of the table data
         if(v>=t && v<m_rows_total && h>=l && h<m_columns_total)
           {
            //--- Color adjustment
            m_columns[c].m_rows[r].Color(m_vcolumns[h].m_colors[v]);
            //--- Value adjustment
            m_columns[c].m_rows[r].Description(m_vcolumns[h].m_vrows[v]);
            v++;
           }
        }
      //---
      h++;
     }
  }

Let's implement the fast rewind when the left mouse button is pressed above the scroll bar buttons similar to how it was done to the input box and list controls. There is no point in displaying the code of the CLabelsTable::FastSwitching() method here since it is much similar to the methods of the same name described in the previous articles in the CListView, CSpinEdit and CCheckBoxEdit classes. 

The code of the methods for processing the control events can now be presented as shown in the listing below: 

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CLabelsTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handling the cursor movement event
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Leave, if the element is hidden
      if(!CElement::IsVisible())
         return;
      //--- Coordinates and the state of the left mouse button
      int x=(int)lparam;
      int y=(int)dparam;
      m_mouse_state=(bool)int(sparam);
      //--- Checking the focus over the table
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      //--- Move the list if the management of the slider is enabled
      if(m_scrollv.ScrollBarControl(x,y,m_mouse_state) || m_scrollh.ScrollBarControl(x,y,m_mouse_state))
         UpdateTable();
      //---
      return;
     }
//--- Handling the pressing on objects
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- If the pressing was on a button of the table scrollbars
      if(m_scrollv.OnClickScrollInc(sparam) || m_scrollv.OnClickScrollDec(sparam) ||
         m_scrollh.OnClickScrollInc(sparam) || m_scrollh.OnClickScrollDec(sparam))
         //--- Shift the table relative to a scroll bar
         UpdateTable();
      //---
      return;
     }
  }
//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CLabelsTable::OnEventTimer(void)
  {
//--- If this is a drop-down element
   if(CElement::IsDropdown())
      FastSwitching();
//--- If this is not a drop-down element, take current availability of the form into consideration
   else
     {
      //--- Track the fast forward of the table only if the form is not blocked
      if(!m_wnd.IsLocked())
         FastSwitching();
     }
  }

A table is a complex GUI control. Therefore, the pointers to its other controls (represented by horizontal and vertical scroll bars in our case) should be included into the pointer database. We should create the personal array of pointers for tables. You can find all these changes in WndContainer.mqh file of CWndContainer class. This topic has already been covered in other articles of the series. Therefore, I will skip it moving on to testing the text label table. 

 


Testing the Text Label Table

For test purposes, we will take the Expert Advisor from the previous article with only the main menu and status bar left intact. In order to add the text label table to an MQL application GUI, the CLabelsTable type class instance, as well as the method and indents from the form's extreme point should be declared (see the code listing below).

class CProgram : public CWndEvents
  {
private:
   //--- Text label table
   CLabelsTable      m_labels_table;
   //---
private:
   //--- Text label table
#define LABELS_TABLE1_GAP_X   (1)
#define LABELS_TABLE1_GAP_Y   (42)
   bool              CreateLabelsTable(void);
  };

Now, let's examine the code of the CProgram::CreateLabelsTable() method in details. We need to make a table consisting of 21 columns and 100 rows. The number of visible columns is 5, while the number of visible rows is 10. We should lock the upper row and the first column to prevent them from being moved. After the table is made, it is filled with random values (from -1000 to 1000) and the colors are defined. Positive values are displayed in green, while negative ones - in red. Be sure to update the table to display the last changes

//+------------------------------------------------------------------+
//| Create the text label table                                      |
//+------------------------------------------------------------------+
bool CProgram::CreateLabelsTable(void)
  {
#define COLUMNS1_TOTAL (21)
#define ROWS1_TOTAL    (100)
//--- Save a pointer to the form
   m_labels_table.WindowPointer(m_window1);
//--- Coordinates
   int x=m_window1.X()+LABELS_TABLE1_GAP_X;
   int y=m_window1.Y()+LABELS_TABLE1_GAP_Y;
//--- Number of visible columns and rows
   int visible_columns_total =5;
   int visible_rows_total    =10;
//--- Set the properties
   m_labels_table.XSize(400);
   m_labels_table.XOffset(40);
   m_labels_table.ColumnXOffset(75);
   m_labels_table.FixFirstRow(true);
   m_labels_table.FixFirstColumn(true);
   m_labels_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
   m_labels_table.VisibleTableSize(visible_columns_total,visible_rows_total);
//--- Create the table
   if(!m_labels_table.CreateLabelsTable(m_chart_id,m_subwin,x,y))
      return(false);
//--- Fill in the table:
//    The first cell is empty
   m_labels_table.SetValue(0,0,"-");
//--- Column headers
   for(int c=1; c<COLUMNS1_TOTAL; c++)
     {
      for(int r=0; r<1; r++)
         m_labels_table.SetValue(c,r,"SYMBOL "+string(c));
     }
//--- Headers for rows, the text alignment is to the right
   for(int c=0; c<1; c++)
     {
      for(int r=1; r<ROWS1_TOTAL; r++)
        m_labels_table.SetValue(c,r,"PARAMETER "+string(r));
     }
//--- Data and table formatting (background and cell colors)
   for(int c=1; c<COLUMNS1_TOTAL; c++)
     {
      for(int r=1; r<ROWS1_TOTAL; r++)
         m_labels_table.SetValue(c,r,string(::rand()%1000-::rand()%1000));
     }
//--- Set the text color in the table cells
   for(int c=1; c<m_labels_table.ColumnsTotal(); c++)
      for(int r=1; r<m_labels_table.RowsTotal(); r++)
         m_labels_table.TextColor(c,r,((double)m_labels_table.GetValue(c,r)>=0) ? clrGreen : clrRed);
//--- Update the table
   m_labels_table.UpdateTable();
//--- Add the element pointer to the base
   CWndContainer::AddToElementsArray(0,m_labels_table);
   return(true);
  }

Also, we should test how the table values are changed when the program runs on a terminal chart. To do this, the code is added to the CProgram::OnTimerEvent() timer of an MQL application class as shown in the listing below: 

//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CProgram::OnTimerEvent(void)
  {
   CWndEvents::OnTimerEvent();
//--- Update the second point of the status line every 500 milliseconds
   static int count=0;
   if(count<500)
     {
      count+=TIMER_STEP_MSC;
      return;
     }
//--- Reset the timer
   count=0;
//--- Change the value in the second point of the status line
   m_status_bar.ValueToItem(1,::TimeToString(::TimeLocal(),TIME_DATE|TIME_SECONDS));
//--- Fill the table with data
   for(int c=1; c<m_labels_table.ColumnsTotal(); c++)
      for(int r=1; r<m_labels_table.RowsTotal(); r++)
         m_labels_table.SetValue(c,r,string(::rand()%1000-::rand()%1000));
//--- Set the text color in the table cells
   for(int c=1; c<m_labels_table.ColumnsTotal(); c++)
      for(int r=1; r<m_labels_table.RowsTotal(); r++)
         m_labels_table.TextColor(c,r,((double)m_labels_table.GetValue(c,r)>=0) ? clrGreen : clrRed);
//--- Update the table
   m_labels_table.UpdateTable();
  }

The method for creating a text label table should be called in the application GUI main method CProgram::CreateExpertPanel(). The abridged version of the method is shown below: 

//+------------------------------------------------------------------+
//| Create the expert panel                                          |
//+------------------------------------------------------------------+
bool CProgram::CreateExpertPanel(void)
  {
//--- Create the form 1 for the controls
//--- Create the controls:
//    Main menu
//--- Context menus
//--- Create the status line
//--- Text label table
   if(!CreateLabelsTable())
      return(false);
//--- Redraw the chart
   m_chart.Redraw();
   return(true);
  }

Now, it is time to compile the code and launch the application on the chart. The screenshot below shows the result:

 Fig. 2. Testing the text label table control

Fig. 2. Testing the text label table control

Everything works fine. Now, let's examine the class for creating the second type of tables. 


 


The Edit Box Table Control

Unlike the text label table, the edit box one provides greater flexibility and has more features. It allows you not only to change the text color but also: 

  • to set the alignment mode for a text in a cell (left/right/center);
  • to change the background color and edit box frames;
  •  to change edit box values manually if the appropriate mode is enabled.

All this makes the table more user-friendly and facilitates its use for a wide range of tasks. Text label tables consist of the following components:

  1. Background
  2. Edit boxes
  3. Vertical scroll bar
  4. Horizontal scroll bar

 Fig. 3. Compound parts of the edit box table control

Fig. 3. Compound parts of the edit box table control


Let's see how the table's code differs from that of the previous one.

 


Developing CTable Class

Let's describe the table properties and highlight its differences from the text label tables. The array of the graphical objects for the table's visible part is of another type here – (CEdit). In other words, it has edit boxes instead of text labels (see the code listing below).

class CTable : public CElement
  {
private:
   //--- Array of objects for the visible part of the table
   struct TEdits
     {
      CEdit             m_rows[];
     };
   TEdits            m_columns[];
  };

There are more unique properties for each cell here, since the table allows you to set/change text alignment method and edit box background color apart from a text color.

class CTable : public CElement
  {
private:
   //--- Arrays of table values and properties
   struct TOptions
     {
      string            m_vrows[];
      ENUM_ALIGN_MODE   m_text_align[];
      color             m_text_color[];
      color             m_cell_color[];
     };
   TOptions          m_vcolumns[];
  };

Below is the list of modes and features that are not present in the text label table.

  • Editable table mode
  • Mode of row highlighting when the cursor is hovering over
  • Selectable row mode
  • Row height
  • Grid color
  • Header background color
  • Header text color
  • Cell color when the cursor hovers over it
  • Cell text default color
  • Default cell text alignment method
  • Highlighted row background color
  • Highlighted row text color

This table type also allows you to lock headers in the first column and first row. When moving the scroll bar sliders, they remain in place if the modes are enabled. The code listing below provides the full list of boxes and methods for setting the table properties: 

class CTable : public CElement
  {
private:
   //--- Height of table rows
   int               m_row_y_size;
   //--- (1) Color of the background and (2) background frame of the table
   color             m_area_color;
   color             m_area_border_color;
   //--- Grid color
   color             m_grid_color;
   //--- Header background color
   color             m_headers_color;
   //--- Header text color
   color             m_headers_text_color;
   //--- Color of cells in different states
   color             m_cell_color;
   color             m_cell_color_hover;
   //--- Default color of cell texts
   color             m_cell_text_color;
   //--- Color of (1) the background and (2) selected row text
   color             m_selected_row_color;
   color             m_selected_row_text_color;
   //--- Editable table mode
   bool              m_read_only;
   //--- Mode of highlighting rows when hovered
   bool              m_lights_hover;
   //--- Selectable row mode
   bool              m_selectable_row;
   //--- Lock (fixation) mode of the first row
   bool              m_fix_first_row;
   //--- Fixation mode of the first column
   bool              m_fix_first_column;
   //--- Default text alignment mode in edit boxes
   ENUM_ALIGN_MODE   m_align_mode;
   //---
public:
   //--- Color of the (1) background and (2) frame of the table
   void              AreaColor(const color clr)                        { m_area_color=clr;                }
   void              BorderColor(const color clr)                      { m_area_border_color=clr;         }
   //--- (1) Get and (2) set the fixation mode of the first row
   bool              FixFirstRow(void)                           const { return(m_fix_first_row);         }
   void              FixFirstRow(const bool flag)                      { m_fix_first_row=flag;            }
   //--- (1) Get and (2) set the fixation mode of the first column
   bool              FixFirstColumn(void)                        const { return(m_fix_first_column);      }
   void              FixFirstColumn(const bool flag)                   { m_fix_first_column=flag;         }
   //--- Color of the (1) header background, (2) header text and (3) table grid
   void              HeadersColor(const color clr)                     { m_headers_color=clr;             }
   void              HeadersTextColor(const color clr)                 { m_headers_text_color=clr;        }
   void              GridColor(const color clr)                        { m_grid_color=clr;                }
   //--- Size of the rows along the Y axis
   void              RowYSize(const int y_size)                        { m_row_y_size=y_size;             }
   void              CellColor(const color clr)                        { m_cell_color=clr;                }
   void              CellColorHover(const color clr)                   { m_cell_color_hover=clr;          }
   //--- (1) "Read only", (2) row highlighting when hovered, (3) selectable row modes
   void              ReadOnly(const bool flag)                         { m_read_only=flag;                }
   void              LightsHover(const bool flag)                      { m_lights_hover=flag;             }
   void              SelectableRow(const bool flag)                    { m_selectable_row=flag;           }
   //--- Cell text alignment method
   void              TextAlign(const ENUM_ALIGN_MODE align_mode)       { m_align_mode=align_mode;         }
  };

Listed below are the features of methods designed to set properties and receive table values by column and row indices.

  • Total table size (total number of columns and rows)
  • Table's visible size (visible number of columns and rows)
  • Cell text alignment method (left/right/center)
  • Text color
  • Background color
  • Set/change the value
  • Receive the value

There is no point in displaying the code for these methods since the similar methods have already been described in the text label table section. After setting the properties, make sure to update the table by calling the CTable::UpdateTable() method, so that implemented changes are displayed. 

class CTable : public CElement
  {
public:
   //--- Set the (1) size of the table and (2) size of its visible part
   void              TableSize(const int columns_total,const int rows_total);
   void              VisibleTableSize(const int visible_columns_total,const int visible_rows_total);
   //--- Set (1) the text alignment mode, (2) text color, (3) cell background color
   void              TextAlign(const int column_index,const int row_index,const ENUM_ALIGN_MODE mode);
   void              TextColor(const int column_index,const int row_index,const color clr);
   void              CellColor(const int column_index,const int row_index,const color clr);
   //--- Set the value to the specified table cell
   void              SetValue(const int column_index,const int row_index,const string value);
   //--- Get the value from the specified table cell
   string            GetValue(const int column_index,const int row_index);
   //--- Update table data with consideration of the recent changes
   void              UpdateTable(void);
  };

Now, let's consider table management methods. They are all private class methods for internal use. Their functions include:

  • Processing pressing a table row.
  • Processing entering a value to a table cell.
  • Receive an ID from an object name.
  • Retrieving a column index from an object name.
  • Retrieving a row index from an object name.
  • Highlighting the selected row.
  • Changing the table row color when the cursor hovers over the button.
  • Fast table data rewind.

Let's start from the CTable::OnClickTableRow() method to process pressing a table row. Several checks should be passed at the start of the method. The program exits the method in the following cases:

  • the editable table mode is enabled;
  • one of the scroll bars is active (the slider is moving);
  • a pressing event does not relate to a table cell. This is determined by the presence of the program name and the table cell membership property in the object name;
  • The control ID does not match. The CTable::IdFromObjectName() method is used to retrieve the ID from the object name. The method has already been described when examining other controls.

Now, it is time to go through all the cells searching for the pressed one by its name while considering the current mode of locked headers (the first row) and the current vertical scroll bar slider position. If the pressed cell is found, the row index and the current cell value are saved to the class fields.

If headers (the first row) have been pressed, the program exits the method. Otherwise, a custom message is generated. It contains a (1) chart ID, (2) event ID (ON_CLICK_LIST_ITEM), (3) control ID and (4) selected row index. 

class CTable : public CElement
  {
private:
   //--- Handling the pressing on the table row
   bool              OnClickTableRow(const string clicked_object);
   //--- Get the identifier from the object name
   int               IdFromObjectName(const string object_name);
  };
//+------------------------------------------------------------------+
//| Handling the pressing on the table row                           |
//+------------------------------------------------------------------+
bool CTable::OnClickTableRow(const string clicked_object)
  {
//--- Leave, if editable table mode is enabled
   if(!m_read_only)
      return(false);
//--- Leave, if the scrollbar is active
   if(m_scrollv.ScrollState() || m_scrollh.ScrollState())
      return(false);
//--- Leave, if the pressing was not on the table cell
   if(::StringFind(clicked_object,CElement::ProgramName()+"_table_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);
//--- Search for the row index
   int row_index=0;
//--- Shift by one index, if the fixed header mode is enabled
   int t=(m_fix_first_row) ? 1 : 0;
//--- Columns
   for(int c=0; c<m_visible_columns_total; c++)
     {
      //--- Get the current position of slider of the vertical scroll bar
      int v=m_scrollv.CurrentPos()+t;
      //--- Rows
      for(int r=t; r<m_visible_rows_total; r++)
        {
         //--- If the pressing was not on this cell
         if(m_columns[c].m_rows[r].Name()==clicked_object)
           {
            //--- Store the row index
            m_selected_item=row_index=v;
            //--- Store the cell line
            m_selected_item_text=m_columns[c].m_rows[r].Description();
            break;
           }
         //--- Increase the row counter
         if(v>=t && v<m_rows_total)
            v++;
        }
     }
//--- Leave, if a header was pressed
   if(m_fix_first_row && row_index<1)
      return(false);
//--- Send a message about it
   ::EventChartCustom(m_chart_id,ON_CLICK_LIST_ITEM,CElement::Id(),m_selected_item,"");
   return(true);
  }

Let's write the CTable::OnEndEditCell() method for processing entering a value into a cell at the editable table mode. The program should also pass a number of checks at the start of the method. Exit from the method is performed in the following cases:

  • the editable table mode is disabled;
  • program name or the table cell membership property does not match;
  • a control ID does not match.

If all checks are passed, we receive the table visible part's cell column and row indices (graphical object array indices) using the CTable::ColumnIndexFromObjectName() and CTable::RowIndexFromObjectName() auxiliary methods. Now, we should add the current positions of the scroll bar sliders to the object indices in order to receive the data array indices. Next, we should correct the row index if the locked header mode is enabled and the object array index is equal to zero. Then, we need to check if the cell value has been changed. If yes, the new values are saved in the appropriate data array and the message containing a (1) chart ID, (2) event ID (ON_END_EDIT), (3) control ID and the (4) line formed of the indices of a column, row and the current cell value is generated. "_" symbol is used as a separator within the line. 

class CTable : public CElement
  {
private:
   //--- Handling entering the value in the table cell
   bool              OnEndEditCell(const string edited_object);
   //--- Retrieve column index from the object name
   int               ColumnIndexFromObjectName(const string object_name);
   //--- Retrieve row index from the object name
   int               RowIndexFromObjectName(const string object_name);
  };
//+------------------------------------------------------------------+
//| Event of finishing editing a cell value                          |
//+------------------------------------------------------------------+
bool CTable::OnEndEditCell(const string edited_object)
  {
//--- Leave, if the editable table mode is disabled
   if(m_read_only)
      return(false);
//--- Leave, if the pressing was not on the table cell
   if(::StringFind(edited_object,CElement::ProgramName()+"_table_edit_",0)<0)
      return(false);
//--- Get the identifier from the object name
   int id=IdFromObjectName(edited_object);
//--- Leave, if the identifier does not match
   if(id!=CElement::Id())
      return(false);
//--- Get the column and row indices of the cell
   int c =ColumnIndexFromObjectName(edited_object);
   int r =RowIndexFromObjectName(edited_object);
//--- Get the column and row indices in the data array
   int vc =c+m_scrollh.CurrentPos();
   int vr =r+m_scrollv.CurrentPos();
//--- Adjust the row index, if a header was pressed
   if(m_fix_first_row && r==0)
      vr=0;
//--- Get the entered value
   string cell_text=m_columns[c].m_rows[r].Description();
//--- If the cell value has been changed
   if(cell_text!=m_vcolumns[vc].m_vrows[vr])
     {
      //--- Store the value in the array
      SetValue(vc,vr,cell_text);
      //--- Send a message about it
      ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),0,string(vc)+"_"+string(vr)+"_"+cell_text);
     }
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Retrieve column index from the object name                       |
//+------------------------------------------------------------------+
int CTable::ColumnIndexFromObjectName(const string object_name)
  {
   ushort u_sep=0;
   string result[];
   int    array_size=0;
//--- Get the code of the separator
   u_sep=::StringGetCharacter("_",0);
//--- Split the string
   ::StringSplit(object_name,u_sep,result);
   array_size=::ArraySize(result)-1;
//--- Checking for exceeding the array range
   if(array_size-3<0)
     {
      ::Print(PREVENTING_OUT_OF_RANGE);
      return(WRONG_VALUE);
     }
//--- Return the item index
   return((int)result[array_size-3]);
  }
//+------------------------------------------------------------------+
//| Retrieve row index from the object name                          |
//+------------------------------------------------------------------+
int CTable::RowIndexFromObjectName(const string object_name)
  {
   ushort u_sep=0;
   string result[];
   int    array_size=0;
//--- Get the code of the separator
   u_sep=::StringGetCharacter("_",0);
//--- Split the string
   ::StringSplit(object_name,u_sep,result);
   array_size=::ArraySize(result)-1;
//--- Checking for exceeding the array range
   if(array_size-2<0)
     {
      ::Print(PREVENTING_OUT_OF_RANGE);
      return(WRONG_VALUE);
     }
//--- Return the item index
   return((int)result[array_size-2]);
  }

The CTable::HighlightSelectedItem() method is necessary for highlighting the row when the appropriate mode is enabled. The highlighted row differs by background and text colors. These editable properties have already been examined previously.

There are two checks at the start of the method (see the code listing below). The program exits from here if:

  • the table cell editing mode is enabled;
  • the row highlighting mode is disabled.

If checks have been passed, a shift by one index is performed for columns and rows if the locked headers modes are enabled. Now, we should find the selected row and set the appropriate color for its cells' background and text in the double loop using two counters (for columns and rows) and considering the current position of the scroll bar sliders. The color from the arrays is used for the objects from other rows.  

class CTable : public CElement
  {
private:
   //--- Highlight the selected row
   void              HighlightSelectedItem(void);
  };
//+------------------------------------------------------------------+
//| Highlight the selected row                                       |
//+------------------------------------------------------------------+
void CTable::HighlightSelectedItem(void)
  {
//--- Leave, if one of the modes ("Read only", "Selectable row") is disabled
   if(!m_read_only || !m_selectable_row)
      return;
//--- Shift by one index, if the fixed header mode is enabled
   int t=(m_fix_first_row) ? 1 : 0;
   int l=(m_fix_first_column) ? 1 : 0;
//--- Get the current position of slider of the horizontal scroll bar
   int h=m_scrollh.CurrentPos()+l;
//--- Columns
   for(int c=l; c<m_visible_columns_total; c++)
     {
      //--- Get the current position of slider of the vertical scroll bar
      int v=m_scrollv.CurrentPos()+t;
      //--- Rows
      for(int r=t; r<m_visible_rows_total; r++)
        {
         //--- Shift of the table data
         if(v>=t && v<m_rows_total)
           {
            //--- Adjustment with consideration of the selected row
            color back_color=(m_selected_item==v) ? m_selected_row_color : m_vcolumns[h].m_cell_color[v];
            color text_color=(m_selected_item==v) ? m_selected_row_text_color : m_vcolumns[h].m_text_color[v];
            //--- Adjust the text and background color of the cell
            m_columns[c].m_rows[r].Color(text_color);
            m_columns[c].m_rows[r].BackColor(back_color);
            v++;
           }
        }
      //---
      h++;
     }
  }

Another useful mode is highlighting the table row when the mouse cursor hovers over it. The CTable::RowColorByHover() method is used for that. It also includes a number of checks. If they are not passed, the program exits the method. Exit is perofrmed in the following cases:

  • the mode of row highlighting when the cursor is hovering over it is disabled;
  • the table editing mode is enabled;
  • the form the control is attached to is blocked;
  • one of the scroll bars is active (the slider is moving).

If all checks are passed, we should go through all cells in a double loop and change their color depending on what row's cell the mouse cursor is currently hovering over. The only exception is a highlighted row. When it is reached, the row counter is increased but the row's cell color is not changed. 

class CTable : public CElement
  {
private:
   //--- Change the table row color when hovered
   void              RowColorByHover(const int x,const int y);
  };
//+-------------------------------------------------------------------+
//| Change the table row color when hovered                           |
//+-------------------------------------------------------------------+
void CTable::RowColorByHover(const int x,const int y)
  {
//--- Leave, if row highlighting when hovered is disabled or if the form is blocked
   if(!m_lights_hover || !m_read_only || m_wnd.IsLocked())
      return;
//--- Leave, if the scroll bar is in the process of moving
   if(m_scrollv.ScrollState() || m_scrollh.ScrollState())
      return;
//--- Shift by one index, if the fixed header mode is enabled
   int t=(m_fix_first_row) ? 1 : 0;
   int l=(m_fix_first_column) ? 1 : 0;
//--- Get the current position of slider of the horizontal scroll bar
   int h=m_scrollh.CurrentPos()+l;
//--- Columns
   for(int c=l; c<m_visible_columns_total; c++)
     {
      //--- Get the current position of slider of the vertical scroll bar
      int v=m_scrollv.CurrentPos()+t;
      //--- Rows
      for(int r=t; r<m_visible_rows_total; r++)
        {
         //--- Check to prevent exceeding the array range
         if(v>=t && v<m_rows_total)
           {
            //--- Skip, if in the "Read only" mode, row selecting is enabled and the selected item is reached
            if(m_selected_item==v && m_read_only && m_selectable_row)
              {
               v++;
               continue;
              }
            //--- Highlight the row, if it is hovered
            if(x>m_columns[0].m_rows[r].X() && x<m_columns[m_visible_columns_total-1].m_rows[r].X2() &&
               y>m_columns[c].m_rows[r].Y() && y<m_columns[c].m_rows[r].Y2())
              {
               m_columns[c].m_rows[r].BackColor(m_cell_color_hover);
              }
            //--- Restore the default color, if the cursor is outside the area of this row
            else
              {
               if(v>=t && v<m_rows_total)
                  m_columns[c].m_rows[r].BackColor(m_vcolumns[h].m_cell_color[v]);
              }
            //---
            v++;
           }
        }
      //---
      h++;
     }
  }

We have examined all table management methods. You can view the details of CTable::OnEvent() control event handler code in the listing below. Please note that CTable::HighlightSelectedItem() method is also used after processing the moving of the vertical scroll bar slider.

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CTable::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 and the state of the left mouse button
      int x=(int)lparam;
      int y=(int)dparam;
      m_mouse_state=(bool)int(sparam);
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      //--- If the scroll bar is active
      if(m_scrollv.ScrollBarControl(x,y,m_mouse_state) || m_scrollh.ScrollBarControl(x,y,m_mouse_state))
         //--- Shift the table
         UpdateTable();
      //--- Highlight the selected row
      HighlightSelectedItem();
      //--- Change the table row color when hovered
      RowColorByHover(x,y);
      return;
     }
//--- Handling the pressing on objects
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- If table row is pressed
      if(OnClickTableRow(sparam))
        {
         //--- Highlight the selected row
         HighlightSelectedItem();
         return;
        }
      //--- If the scroll bar button was pressed
      if(m_scrollv.OnClickScrollInc(sparam) || m_scrollv.OnClickScrollDec(sparam) ||
         m_scrollh.OnClickScrollInc(sparam) || m_scrollh.OnClickScrollDec(sparam))
        {
         //--- Update table data with consideration of the recent changes
         UpdateTable();
         //--- Highlight the selected row
         HighlightSelectedItem();
         return;
        }
      return;
     }
//--- Handling of the changing the value in the entry field event
   if(id==CHARTEVENT_OBJECT_ENDEDIT)
     {
      OnEndEditCell(sparam);
      //--- Reset table colors
      ResetColors();
      return;
     }
  }

 


Testing the Edit Box Table

Everything is ready to test the edit box table. Let's make a copy of the previous test EA and delete all elements related to the text label table. Now, create the instance of the CTable type class in the CProgram custom class and declare the method for creating the table and indents from the form extreme point:

class CProgram : public CWndEvents
  {
private:
   //--- The edit box table
   CTable            m_table;
   //---
private:
   //--- The edit box table
#define TABLE1_GAP_X          (1)
#define TABLE1_GAP_Y          (42)
   bool              CreateTable(void);
  };

Let's make a table consisting of 100 columns and 1000 rows. The number of visible columns is 6, while the number of visible rows is 15. Let's lock the table headers (the first row and column), so that they remain in place when the scroll bar sliders are moving. We should enable the modes of row selection and row highlighting when the cursor hovers over it. 

After creating the control, fill the table arrays with data and format it. For example, let's apply the ALIGN_RIGHT alignment method to the first column cell text. Let the row cell backgrounds be colored in "zebra" style, while the column text will be colored in alternating red and blue colors. Be sure to update the table to display changes. 

//+------------------------------------------------------------------+
//| Create the table                                                 |
//+------------------------------------------------------------------+
bool CProgram::CreateTable(void)
  {
#define COLUMNS1_TOTAL (100)
#define ROWS1_TOTAL    (1000)
//--- Save the pointer to the form
   m_table.WindowPointer(m_window1);
//--- Coordinates
   int x=m_window1.X()+TABLE1_GAP_X;
   int y=m_window1.Y()+TABLE1_GAP_Y;
//--- Number of visible columns and rows
   int visible_columns_total =6;
   int visible_rows_total    =15;
//--- Set the properties before creating
   m_table.XSize(600);
   m_table.RowYSize(20);
   m_table.FixFirstRow(true);
   m_table.FixFirstColumn(true);
   m_table.LightsHover(true);
   m_table.SelectableRow(true);
   m_table.TextAlign(ALIGN_CENTER);
   m_table.HeadersColor(C'255,244,213');
   m_table.HeadersTextColor(clrBlack);
   m_table.GridColor(clrLightGray);
   m_table.CellColorHover(clrGold);
   m_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
   m_table.VisibleTableSize(visible_columns_total,visible_rows_total);
//--- Create the control element
   if(!m_table.CreateTable(m_chart_id,m_subwin,x,y))
      return(false);
//--- Fill in the table:
//    The first cell is empty
   m_table.SetValue(0,0,"-");
//--- Column headers
   for(int c=1; c<COLUMNS1_TOTAL; c++)
     {
      for(int r=0; r<1; r++)
         m_table.SetValue(c,r,"SYMBOL "+string(c));
     }
//--- Headers for rows, the text alignment is to the right
   for(int c=0; c<1; c++)
     {
      for(int r=1; r<ROWS1_TOTAL; r++)
        {
         m_table.SetValue(c,r,"PARAMETER "+string(r));
         m_table.TextAlign(c,r,ALIGN_RIGHT);
        }
     }
//---  Data and table formatting (background and cell colors)
   for(int c=1; c<COLUMNS1_TOTAL; c++)
     {
      for(int r=1; r<ROWS1_TOTAL; r++)
        {
         m_table.SetValue(c,r,string(c)+":"+string(r));
         m_table.TextColor(c,r,(c%2==0)? clrRed : clrRoyalBlue);
         m_table.CellColor(c,r,(r%2==0)? clrWhiteSmoke : clrWhite);
        }
     }
//--- Update the table to display the changes
   m_table.UpdateTable();
//--- Add the object to the total array of object groups
   CWndContainer::AddToElementsArray(0,m_table);
   return(true);
  }

The CProgram::CreateTable() method should be called in the application GUI main method (see the abridged version in the listing below): 

//+------------------------------------------------------------------+
//| Create the expert panel                                          |
//+------------------------------------------------------------------+
bool CProgram::CreateExpertPanel(void)
  {
//--- Create the form 1 for the controls
//--- Create the controls:
//    Main menu
//--- Context menus
//--- Create the status line
//--- Edit box table
   if(!CreateTable())
      return(false);
//--- Redraw the chart
   m_chart.Redraw();
   return(true);
  }

Compile the program and run it on a chart. If everything is done correctly, we will get the result as shown in the screenshot below:

 Fig. 4. Testing the edit box table control

Fig. 4. Testing the edit box table control

Everything works fine. However, if you place a text exceeding 63 characters in a single cell, the text is not displayed in full. All graphical objects of the terminal capable of displaying a text have a limitation of 63 symbols. Use the CCanvas class for drawing to bypass it. This class contains the text display methods without any limitations. We have already applied it when drawing some controls (separation line and context menu) in the following articles:

Since 63 characters may often be insufficient for data display, the third table type is drawn using the CCanvas class. 

 


The Rendered Table Control

A rendered table has no limitations on the number of characters at each cell. Besides, a width of each column can be configured without changing a visible table width, which is quite difficult to achieve when working with other table types discussed above. Thus, we already can note a few advantages of a rendered table:

  • no limitations on the number of symbols in each cell;
  • each column's width can be set separately;
  • only one object (OBJ_BITMAP_LABEL) is used to create a table instead of multiple objects like text labels (OBJ_LABEL) or edit boxes (OBJ_EDIT).

Text label tables consist of the following components:

  1. Background
  2. Rendered table
  3. Vertical scroll bar
  4. Horizontal scroll bar

Fig. 5. Compound parts of the rendered table control
 

Fig. 5. Compound parts of the rendered table control

Let's examine the code of the class for creating such a table.

 


Developing CCanvasTable Class

Let's create CTOptions structure to store the table values and properties:

//+------------------------------------------------------------------+
//| Class for creating a rendered table                              |
//+------------------------------------------------------------------+
class CCanvasTable : public CElement
  {
private:
   //--- Array of values and properties of the table
   struct CTOptions
     {
      string            m_vrows[];
      int               m_width;
      ENUM_ALIGN_MODE   m_text_align;
     };
   CTOptions         m_vcolumns[];
  };

CTOptions structure fields are initialized using default values when the table's basic size (total number of columns and rows) is set. We will set the width of all columns to 100 pixels, while the text alignment method in column cells will be ALIGN_CENTER

class CCanvasTable : public CElement
  {
public:
   //--- Set the size of the table
   void              TableSize(const int columns_total,const int rows_total);
  };
//+------------------------------------------------------------------+
//| Set the size of the table                                        |
//+------------------------------------------------------------------+
void CCanvasTable::TableSize(const int columns_total,const int rows_total)
  {
//--- There must be at least one column
   m_columns_total=(columns_total<1) ? 1 : columns_total;
//--- There must be at least two rows
   m_rows_total=(rows_total<2) ? 2 : rows_total;
//--- Set the size of the columns array
   ::ArrayResize(m_vcolumns,m_columns_total);
//--- Set the size of the rows arrays
   for(int i=0; i<m_columns_total; i++)
     {
      ::ArrayResize(m_vcolumns[i].m_vrows,m_rows_total);
      //--- Initialize the column properties with the default values
      m_vcolumns[i].m_width      =100;
      m_vcolumns[i].m_text_align =ALIGN_CENTER;
     }
  }

Now, let's create the methods allowing to change the width or text alignment method of some columns (if necessary) after the table size has already been set. To do this, you need to initialize your array and simply pass it to the corresponding method. The example is shown below. 

class CCanvasTable : public CElement
  {
public:
   //--- Set the (1) text alignment mode and (2) width for each column
   void              TextAlign(const ENUM_ALIGN_MODE &array[]);
   void              ColumnsWidth(const int &array[]);
  };
//+------------------------------------------------------------------+
//| Fills the array of text alignment modes                          |
//+------------------------------------------------------------------+
void CCanvasTable::TextAlign(const ENUM_ALIGN_MODE &array[])
  {
   int total=0;
   int array_size=::ArraySize(array);
//--- Leave, if a zero-sized array was passed
   if(array_size<1)
      return;
//--- Adjust the value to prevent the array exceeding the range
   total=(array_size<m_columns_total)? array_size : m_columns_total;
//--- Store the values in structure
   for(int c=0; c<total; c++)
      m_vcolumns[c].m_text_align=array[c];
  }
//+------------------------------------------------------------------+
//| Fills the array of column widths                                 |
//+------------------------------------------------------------------+
void CCanvasTable::ColumnsWidth(const int &array[])
  {
   int total=0;
   int array_size=::ArraySize(array);
//--- Leave, if a zero-sized array was passed
   if(array_size<1)
      return;
//--- Adjust the value to prevent the array exceeding the range 
   total=(array_size<m_columns_total)? array_size : m_columns_total;
//--- Store the values in the structure
   for(int c=0; c<total; c++)
      m_vcolumns[c].m_width=array[c];
  }

Before we start creating the table, we should set its overall size, as well as the size of its visible part, relative to specified parameters (width of all columns, height of all rows and presence of scroll bars). To do this, we will write the CCanvasTable::CalculateTableSize() method shown in the below listing: 

class CCanvasTable : public CElement
  {
private:
   //--- Total size and size of the visible part of the table
   int               m_table_x_size;
   int               m_table_y_size;
   int               m_table_visible_x_size;
   int               m_table_visible_y_size;
//---
private:
   //--- Calculate the table size
   void              CalculateTableSize(void);
  };
//+------------------------------------------------------------------+
//| Calculate the size of the table                                  |
//+------------------------------------------------------------------+
void CCanvasTable::CalculateTableSize(void)
  {
//--- Calculate the total width of the table
   m_table_x_size=0;
   for(int c=0; c<m_columns_total; c++)
      m_table_x_size=m_table_x_size+m_vcolumns[c].m_width;
//--- Width of the table with a vertical scroll bar
   int x_size=(m_rows_total>m_visible_rows_total) ? m_x_size-m_scrollh.ScrollWidth() : m_x_size-2;
//--- If the width of all columns is less than the table width, use the table width
   if(m_table_x_size<m_x_size)
      m_table_x_size=x_size;
//--- Calculate the total height of the table
   m_table_y_size=m_cell_y_size*m_rows_total-(m_rows_total-1);
//--- Set the frame size to display a fragment of the image (visible part of the table)
   m_table_visible_x_size=x_size;
   m_table_visible_y_size=m_cell_y_size*m_visible_rows_total-(m_visible_rows_total-1);
//--- If there is a horizontal scroll bar, adjust the control size along the Y axis
   int y_size=m_cell_y_size*m_visible_rows_total+2-(m_visible_rows_total-1);
   m_y_size=(m_table_x_size>m_table_visible_x_size) ? y_size+m_scrollh.ScrollWidth()-1 : y_size;
  }

After calculating the table size and creating the canvas, we need to develop the methods for drawing the table grid (frame) and cell text. To draw the grid, we will write the CCanvasTable::DrawGrid() method. First, horizontal grid lines are drawn in the first loop, then the vertical lines are drawn in the second loop.  

class CCanvasTable : public CElement
  {
private:
   //--- Grid color
   color             m_grid_color;
   //--- Size (height) of cells
   int               m_cell_y_size;
//---
public:
   //--- Grid color
   void              GridColor(const color clr)           { m_grid_color=clr;                }
//---
private:
   //--- Draw grid
   void              DrawGrid(void);
  };
//+------------------------------------------------------------------+
//| Draw grid                                                        |
//+------------------------------------------------------------------+
void CCanvasTable::DrawGrid(void)
  {
//--- Grid color
   uint clr=::ColorToARGB(m_grid_color,255);
//--- Size of canvas for drawing
   int x_size =m_canvas.XSize()-1;
   int y_size =m_canvas.YSize()-1;
//--- Coordinates
   int x1=0,x2=0,y1=0,y2=0;
//--- Horizontal lines
   x1=0;
   y1=0;
   x2=x_size;
   y2=0;
   for(int i=0; i<=m_rows_total; i++)
     {
      m_canvas.Line(x1,y1,x2,y2,clr);
      y2=y1+=m_cell_y_size-1;
     }
//--- Vertical lines
   x1=0;
   y1=0;
   x2=0;
   y2=y_size;
   for(int i=0; i<m_columns_total; i++)
     {
      m_canvas.Line(x1,y1,x2,y2,clr);
      x2=x1+=m_vcolumns[i].m_width;
     }
//--- Right
   x1=x_size;
   y1=0;
   x2=x_size;
   y2=y_size;
   m_canvas.Line(x1,y1,x2,y2,clr);
  }

The CCanvasTable::DrawText() method for drawing a text is more complicated than the grid drawing method. We should not only consider the current column's text alignment method but also the previous column's one to calculate the indents correctly. We will set the indent of 10 pixels from a cell edge for the right and left alignment. The indent from the upper cell edge will be 3 pixels. The method code is provided in details in the below listing: 

class CCanvasTable : public CElement
  {
private:
   //--- Text color
   color             m_cell_text_color;
//---
public:
   //--- Table text color
   void              TextColor(const color clr)           { m_cell_text_color=clr;           }
//---
private:
   //--- Draw text
   void              DrawText(void);
  };
//+------------------------------------------------------------------+
//| Draw text                                                        |
//+------------------------------------------------------------------+
void CCanvasTable::DrawText(void)
  {
//--- To calculate the coordinates and offsets
   int  x             =0;
   int  y             =0;
   uint text_align    =0;
   int  column_offset =0;
   int  cell_x_offset =10;
   int  cell_y_offset =3;
//--- Text color
   uint clr=::ColorToARGB(m_cell_text_color,255);
//--- Font properties
   m_canvas.FontSet(FONT,-80,FW_NORMAL);
//--- Columns
   for(int c=0; c<m_columns_total; c++)
     {
      //--- Offset calculation for the first column
      if(c==0)
        {
         //--- Text alignment in cells based on the mode set for each column
         switch(m_vcolumns[0].m_text_align)
           {
            //--- Center
            case ALIGN_CENTER :
               column_offset=column_offset+m_vcolumns[0].m_width/2;
               x=column_offset;
               break;
               //--- Right
            case ALIGN_RIGHT :
               column_offset=column_offset+m_vcolumns[0].m_width;
               x=column_offset-cell_x_offset;
               break;
               //--- Left
            case ALIGN_LEFT :
               x=column_offset+cell_x_offset;
               break;
           }
        }
      //--- Calculation of offsets for all columns except the first
      else
        {
         //--- Text alignment in cells based on the mode set for each column
         switch(m_vcolumns[c].m_text_align)
           {
            //--- Center
            case ALIGN_CENTER :
               //--- Calculation of offset relative to the alignment in the previous column
               switch(m_vcolumns[c-1].m_text_align)
                 {
                  case ALIGN_CENTER :
                     column_offset=column_offset+(m_vcolumns[c-1].m_width/2)+(m_vcolumns[c].m_width/2);
                     break;
                  case ALIGN_RIGHT :
                     column_offset=column_offset+(m_vcolumns[c].m_width/2);
                     break;
                  case ALIGN_LEFT :
                     column_offset=column_offset+m_vcolumns[c-1].m_width+(m_vcolumns[c].m_width/2);
                     break;
                 }
               //---
               x=column_offset;
               break;
               //--- Right
            case ALIGN_RIGHT :
               //--- Calculation of offset relative to the alignment in the previous column
               switch(m_vcolumns[c-1].m_text_align)
                 {
                  case ALIGN_CENTER :
                     column_offset=column_offset+(m_vcolumns[c-1].m_width/2)+m_vcolumns[c].m_width;
                     x=column_offset-cell_x_offset;
                     break;
                  case ALIGN_RIGHT :
                     column_offset=column_offset+m_vcolumns[c].m_width;
                     x=column_offset-cell_x_offset;
                     break;
                  case ALIGN_LEFT :
                     column_offset=column_offset+m_vcolumns[c-1].m_width+m_vcolumns[c].m_width;
                     x=column_offset-cell_x_offset;
                     break;
                 }
               //---
               break;
               //--- Left
            case ALIGN_LEFT :
               //--- Calculation of offset relative to the alignment in the previous column
               switch(m_vcolumns[c-1].m_text_align)
                 {
                  case ALIGN_CENTER :
                     column_offset=column_offset+(m_vcolumns[c-1].m_width/2);
                     x=column_offset+cell_x_offset;
                     break;
                  case ALIGN_RIGHT :
                     x=column_offset+cell_x_offset;
                     break;
                  case ALIGN_LEFT :
                     column_offset=column_offset+m_vcolumns[c-1].m_width;
                     x=column_offset+cell_x_offset;
                     break;
                 }
               //---
               break;
           }
        }
      //--- Rows
      for(int r=0; r<m_rows_total; r++)
        {
         //---
         y+=(r>0) ? m_cell_y_size-1 : cell_y_offset;
         //---
         switch(m_vcolumns[c].m_text_align)
           {
            case ALIGN_CENTER :
               text_align=TA_CENTER|TA_TOP;
               break;
            case ALIGN_RIGHT :
               text_align=TA_RIGHT|TA_TOP;
               break;
            case ALIGN_LEFT :
               text_align=TA_LEFT|TA_TOP;
               break;
           }
         //--- Draw text
         m_canvas.TextOut(x,y,m_vcolumns[c].m_vrows[r],clr,text_align);
        }
      //--- Zero the Y coordinate for the next cycle
      y=0;
     }
  }

In the first two table types we have examined, the data shift via scroll bars is performed by changing the object values (text labels and edit boxes) of the table's visible part. Here we will shift the rectangular visibility scope of the image. In other words, the table (image) size is initially equal to the sum of all columns and the height of all rows. This is the size of the original image, within which the visibility scope can be moved. The visibility scope size can be changed at any time, but here they are set right after the control is created in the CCanvasTable::CreateCells() method. 

The visibility scope can be set via OBJPROP_XSIZE and OBJPROP_YSIZE properties, while the shift of the scope (within the original image range) can be performed via OBJPROP_XOFFSET and OBJPROP_YOFFSET properties (see the example in the code listing below): 

//--- Set the size of the visible area
   ::ObjectSetInteger(m_chart_id,name,OBJPROP_XSIZE,m_table_visible_x_size);
   ::ObjectSetInteger(m_chart_id,name,OBJPROP_YSIZE,m_table_visible_y_size);
//--- Set the frame offset within the image along the X and Y axes
   ::ObjectSetInteger(m_chart_id,name,OBJPROP_XOFFSET,0);
   ::ObjectSetInteger(m_chart_id,name,OBJPROP_YOFFSET,0);

Let's develop a simple method CCanvasTable::ShiftTable() to shift the visibility scope relative to the current position of the scroll bar sliders. The vertical shift step is equal to the row height, while the horizontal one is performed in pixels (see the code listing below): 

class CCanvasTable : public CElement
  {
public:
   //--- Shift the table relative to the positions of scroll bars
   void              ShiftTable(void);
  };
//+------------------------------------------------------------------+
//| Shift the table relative to the scrollbars                       |
//+------------------------------------------------------------------+
void CCanvasTable::ShiftTable(void)
  {
//--- Get the current positions of sliders of the vertical and horizontal scroll bars
   int h=m_scrollh.CurrentPos();
   int v=m_scrollv.CurrentPos();
//--- Calculation of the table position relative to the scroll bar sliders
   long c=h;
   long r=v*(m_cell_y_size-1);
//--- Shift of the table
   ::ObjectSetInteger(m_chart_id,m_canvas.Name(),OBJPROP_XOFFSET,c);
   ::ObjectSetInteger(m_chart_id,m_canvas.Name(),OBJPROP_YOFFSET,r);
  }

The general CCanvasTable::DrawTable() method code for drawing the table will look as shown in the below listing: 

class CCanvasTable : public CElement
  {
public:
   //--- Draws the table with consideration of the recent changes
   void              DrawTable(void);
  };
//+------------------------------------------------------------------+
//| Draw table                                                       |
//+------------------------------------------------------------------+
void CCanvasTable::DrawTable(void)
  {
//--- Make the background transparent
   m_canvas.Erase(::ColorToARGB(clrNONE,0));
//--- Draw grid
   DrawGrid();
//--- Draw text
   DrawText();
//--- Display the latest drawn changes
   m_canvas.Update();
//--- Shift the table relative to the scroll bars
   ShiftTable();
  }

Now, all is ready to test this table type. 

 


Testing the Rendered Table

Let's make a copy of the previous test EA and delete all elements related to the CTable table type. Now, create the instance of the CCanvasTable type class in the CProgram custom class and declare the (1) CProgram::CreateCanvasTable() method for creating the table as well as the (2) indents from the form extreme point as shown in the code listing below:

class CProgram : public CWndEvents
  {
private:
   //--- Rendered table
   CCanvasTable      m_canvas_table;
   //---
private:
   //--- Rendered table
#define TABLE1_GAP_X          (1)
#define TABLE1_GAP_Y          (42)
   bool              CreateCanvasTable(void);
  };

Let's make a table consisting of 15 columns and 1000 rows. The number of visible rows will be 16. We do not have to set the number of visible columns since the horizontal shift is performed in pixels. In this case, the width of the table's visible area should be specified explicitly. Let's set it to 601 pixels. 

As an example, let's set the width of all columns (except for the first two ones) to 70 pixels. The width of the first and second columns are set to 100 and 90 pixels accordingly. In all columns (except for the first three ones), the text is centered. In the first and third columns it is aligned to the right, while in the second one it is aligned to the left. The full code of the CProgram::CreateCanvasTable() method is provided in the below listing:

//+------------------------------------------------------------------+
//| Create the rendered table                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateCanvasTable(void)
  {
#define COLUMNS1_TOTAL 15
#define ROWS1_TOTAL    1000
//--- Save the pointer to the form
   m_canvas_table.WindowPointer(m_window1);
//--- Coordinates
   int x=m_window1.X()+TABLE1_GAP_X;
   int y=m_window1.Y()+TABLE1_GAP_Y;
//--- Number of visible rows
   int visible_rows_total=16;
//--- Column width array
   int width[COLUMNS1_TOTAL];
   ::ArrayInitialize(width,70);
   width[0]=100;
   width[1]=90;
//--- Array for text alignment in columns
   ENUM_ALIGN_MODE align[COLUMNS1_TOTAL];
   ::ArrayInitialize(align,ALIGN_CENTER);
   align[0]=ALIGN_RIGHT;
   align[1]=ALIGN_LEFT;
   align[2]=ALIGN_RIGHT;
//--- Set the properties before creating
   m_canvas_table.XSize(601);
   m_canvas_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
   m_canvas_table.VisibleTableSize(0,visible_rows_total);
   m_canvas_table.TextAlign(align);
   m_canvas_table.ColumnsWidth(width);
   m_canvas_table.GridColor(clrLightGray);
//--- Fill the table with data
   for(int c=0; c<COLUMNS1_TOTAL; c++)
      for(int r=0; r<ROWS1_TOTAL; r++)
         m_canvas_table.SetValue(c,r,string(c)+":"+string(r));
//--- Create the control
   if(!m_canvas_table.CreateTable(m_chart_id,m_subwin,x,y))
      return(false);
//--- Add the object to the total array of object groups
   CWndContainer::AddToElementsArray(0,m_canvas_table);
   return(true);
  }

The call is performed in the main GUI creation method: 

//+------------------------------------------------------------------+
//| Create the trading panel                                         |
//+------------------------------------------------------------------+
bool CProgram::CreateExpertPanel(void)
  {
//--- Create the form 1 for the controls
//--- Create the controls:
//    Main menu
//--- Context menus
//--- Create the status line
//--- Create the rendered table
   if(!CreateCanvasTable())
      return(false);
//--- Redraw the chart
   m_chart.Redraw();
   return(true);
  }

Now, let's compile the program and run it on a chart. The result is shown on the below screenshot:

 Fig. 6. Testing the rendered table control in the EA

Fig. 6. Testing the rendered table control in the EA

In the fifth chapter of the first part of the series, we tested usage of forms in scripts. Tables without scroll bars can be used in this type of MQL applications. For example, let's add the rendered table to the script form and update data each 250 milliseconds. Add the table code to the application custom class as shown earlier. Besides, the code should be added to CProgram::OnEvent() script event handler as displayed in the listing below. Now, the data in the second column will change continuously after a specified time interval. 

//+------------------------------------------------------------------+
//| Events                                                           |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int milliseconds)
  {
   static int count =0;  // Counter
   string     str   =""; // Header row
//--- Form the header displaying the process
   switch(count)
     {
      case 0 : str="SCRIPT PANEL";     break;
      case 1 : str="SCRIPT PANEL .";   break;
      case 2 : str="SCRIPT PANEL ..";  break;
      case 3 : str="SCRIPT PANEL ..."; break;
     }
//--- Update the header row
   m_window.CaptionText(str);
//--- Change the first column data
   for(int r=0; r<13; r++)
      m_canvas_table.SetValue(1,r,string(::rand()));
//--- Display the new table data
   m_canvas_table.DrawTable();
//--- Redraw the chart
   m_chart.Redraw();
//--- Increase the counter
   count++;
//--- Set to zero if exceeding three
   if(count>3)
      count=0;
//--- Pause
   ::Sleep(milliseconds);
  }

Compile the program and run the script on the chart. You should see the following: 

 Fig. 7. Testing the rendered table control in the script

Fig. 7. Testing the rendered table control in the script

We have completed the development of the CCanvasTable class for creating a rendered table. Now, it is time for some provisional results. 

 


Conclusion

In this article, we have discussed the three classes for creating the important interface controls – Tables. Each of these classes has its own features that are best suited for specific tasks. For example, the CTable class allows developing a table with editable boxes while providing an opportunity to format it so that it looks more user-friendly. The CCanvasTable class allows you to avoid the limitation on the number of characters in cells, while shifting the table along the image visibility scope makes it possible to set specific width to each of the columns. These are not the final versions of the tables. If necessary, they can be improved further.

In the next article, we will examine in details the classes for developing Tabs controls that are also frequently used in graphical interfaces.

You can download the material of the part VII below and test how it works. If you have questions on using material from 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 of this article.

List of articles (chapters) of the seventh part:


Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/2500

Attached files |
Last comments | Go to discussion (4)
Ahmad Yani
Ahmad Yani | 6 Aug 2016 at 01:36
MetaQuotes Software Corp.:

New article Graphical Interfaces VII: the Tables Controls (Chapter 1) has been published:

Author: Anatoli Kazharski

Awesome!
reboeiras
reboeiras | 19 Aug 2016 at 00:18
Got the following error messages when I tried to compile the code in  MetaTrader 5.0 Build 1347. 
I'm new to  programming and don't know what happened. Here are the errors and the respective lines where they occur.

'return' - cannot convert from const pointer to nonconst pointer SplitButon.mqh 90 65
  CContextMenu     *GetContextMenuPointer(void)        const { return(::GetPointer(m_drop_menu));  }

'return' - cannot convert from const pointer to nonconst pointer ListView.mqh 67 76
  CScrollV         *GetScrollVPointer(void)                       const { return(::GetPointer(m_scrollv)); }

'return' - cannot convert from const pointer to nonconst pointer CheckComboBox.mqh 90 81
 CListView        *GetListViewPointer(void)                           const { return(::GetPointer(m_listview));  

'return' - cannot convert from const pointer to nonconst pointer LabelsTable.mqh 78 69
   CScrollV         *GetScrollVPointer(void)                const { return(::GetPointer(m_scrollv)); }

'return' - cannot convert from const pointer to nonconst pointer LabelsTable.mqh 79 69
     CScrollH         *GetScrollHPointer(void)                const { return(::GetPointer(m_scrollh)); }

'return' - cannot convert from const pointer to nonconst pointer Table.mqh 101 74 e 102 74

   CScrollV         *GetScrollVPointer(void)                     const { return(::GetPointer(m_scrollv)); }
   CScrollH         *GetScrollHPointer(void)                     const { return(::GetPointer(m_scrollh)); }
 
'return' - cannot convert from const pointer to nonconst pointer CanvasTable.mqh 75 61 e 76 61
   CScrollV         *GetScrollVPointer(void)        const { return(::GetPointer(m_scrollv)); }
   CScrollH         *GetScrollHPointer(void)        const { return(::GetPointer(m_scrollh)); }

Anatoli Kazharski
Anatoli Kazharski | 30 Aug 2016 at 07:41
reboeiras:
Got the following error messages when I tried to compile the code in  MetaTrader 5.0 Build 1347. 
I'm new to  programming and don't know what happened. Here are the errors and the respective lines where they occur.

...

Download the latest version: Graphical Interfaces X: Updates for Easy And Fast Library (Build 2) 

 

Alireza2049
Alireza2049 | 28 Jun 2023 at 16:27

Hello

Thank you very much for the excellent article.

I have a question

How can I use checkboxes or buttons in some cells in a table?

I tried a lot to do it but unfortunately I didn't succeed
Testing trading strategies on real ticks Testing trading strategies on real ticks
The article provides the results of testing a simple trading strategy in three modes: "1 minute OHLC", "Every tick" and "Every tick based on real ticks" using actual historical data.
The checks a trading robot must pass before publication in the Market The checks a trading robot must pass before publication in the Market
Before any product is published in the Market, it must undergo compulsory preliminary checks in order to ensure a uniform quality standard. This article considers the most frequent errors made by developers in their technical indicators and trading robots. An also shows how to self-test a product before sending it to the Market.
Graphical Interfaces VII: The Tabs Control (Chapter 2) Graphical Interfaces VII: The Tabs Control (Chapter 2)
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.
LifeHack for Traders: Indicators of Balance, Drawdown, Load and Ticks during Testing LifeHack for Traders: Indicators of Balance, Drawdown, Load and Ticks during Testing
How to make the testing process more visual? The answer is simple: you need to use one or more indicators in the Strategy Tester, including a tick indicator, an indicator of balance and equity, an indicator of drawdown and deposit load. This solution will help you visually track the nature of ticks, balance and equity changes, as well as drawdown and deposit load.