Graphical Interfaces VII: the Tables Controls (Chapter 1)
Contents
- Introduction
- The Text Label Table Control
- The Edit Box Table Control
- The Rendered Table Control
- Conclusion
Introduction
The first article Graphical Interfaces I: Preparation of the Library Structure (Chapter 1) explains in detail what this library is for. You will find a list of articles with links at the end of each chapter. There, you can also download a complete version of the library at the current stage of development. The files must be placed in the same directories as they are located in the archive.
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:
- Background.
- Text labels.
- Vertical scroll bar.
- Horizontal scroll bar.
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
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:
- Background
- Edit boxes
- Vertical scroll bar
- Horizontal scroll bar
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
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:
- Graphical Interfaces II: the Separation Line and Context Menu Elements (Chapter 2)
- Graphical Interfaces IV: Informational Interface Elements (Chapter 1)
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:
- Background
- Rendered table
- Vertical scroll bar
- Horizontal scroll bar
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
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
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:
- Graphical Interfaces VII: the Tables Controls (Chapter 1)
- Graphical Interfaces VII: the Tabs Control (Chapter 2)
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/2500
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
New article Graphical Interfaces VII: the Tables Controls (Chapter 1) has been published:
Author: Anatoli Kazharski
Download the latest version: Graphical Interfaces X: Updates for Easy And Fast Library (Build 2)
Hello
Thank you very much for the excellent article.
I have a questionHow 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