Graphical Interfaces XI: Text edit boxes and Combo boxes in table cells (build 15)

Anatoli Kazharski | 28 September, 2017

Contents


Introduction

The first article Graphical Interfaces I: Preparation of the Library Structure (Chapter 1) considers in detail what this library is for. The full version of the library at the current stage of development is attached at the end of each article. Files must be placed under the same directories as they are located in the archive.

The next update focuses on the Table control (the CTable class). Previously it became possible to add check boxes and buttons to table cells. Let us expand the lineup of these controls with text boxes and combo boxes. The new build also adds the ability to manage the window sizes during the application runtime.


Resizing the window

To facilitate the use of list views, tables or multiline text boxes, it is often necessary to reduce the window or maximize it to the entire chart. There are multiple ways of managing the window size.

Let us see how this is implemented in the library. 

A separate instance of the CButton class has been declared for creating the full screen mode button. The public CButton::GetFullscreenButtonPointer() method is designed to get the pointer to the button. This button is disabled on the form by default. Use the CButton::FullscreenButtonIsUsed() method to enable the button. 

//+------------------------------------------------------------------+
//| The Form Class for Controls                                      |
//+------------------------------------------------------------------+
class CWindow : public CElement
  {
private:
   //--- Objects for creating a form
   CButton           m_button_fullscreen;
   //--- Presence of the button for maximizing the window into full screen mode
   bool              m_fullscreen_button;
   //---
public:
   //--- Returns pointers to form buttons
   CButton          *GetFullscreenButtonPointer(void)                { return(::GetPointer(m_button_fullscreen)); }
   //--- Use the full screen button
   void              FullscreenButtonIsUsed(const bool state)        { m_fullscreen_button=state;                 }
   bool              FullscreenButtonIsUsed(void)              const { return(m_fullscreen_button);               }
  };

The full screen button is created in the general method CWindow::CreateButtons() (see the code listing below), where all enabled buttons of the form are created. Similar to the buttons for showing tooltips and minimizing the form, the full screen mode button can only be used in the main window. 

//+------------------------------------------------------------------+
//| Creates buttons on the form                                      |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Controls\\full_screen.bmp"
#resource "\\Images\\EasyAndFastGUI\\Controls\\minimize_to_window.bmp"
//---
bool CWindow::CreateButtons(void)
  {
//--- If the program type is script, leave
   if(CElementBase::ProgramType()==PROGRAM_SCRIPT)
      return(true);
//--- Counter, size, number
   int i=0,x_size=20;
   int buttons_total=4;
//--- The path to the file
   string icon_file="";
//--- Exception in the capture area
   m_right_limit=0;
//---
   CButton *button_obj=NULL;
//---
   for(int b=0; b<buttons_total; b++)
     {
      ...
      else if(b==1)
        {
         m_button_fullscreen.MainPointer(this);
         //--- Leave, if (1) the button is not enabled or (2) it is a dialog box
         if(!m_fullscreen_button || m_window_type==W_DIALOG)
            continue;
         //---
         button_obj=::GetPointer(m_button_fullscreen);
         icon_file="Images\\EasyAndFastGUI\\Controls\\full_screen.bmp";
        }
      ...
     }
//---
   return(true);
  }

The minimum size of the window is automatically set to the size specified when creating the control. But these values can be overridden. In the current version, it is not possible to set the window size to less than 200x200 pixels. This is controlled in the method for initializing the control's properties — CWindow::InitializeProperties(). 

class CWindow : public CElement
  {
private:
   //--- Minimum window size
   int               m_minimum_x_size;
   int               m_minimum_y_size;
   //---
public:
   //--- Setting the minimum window size
   void              MinimumXSize(const int x_size)                  { m_minimum_x_size=x_size;                   }
   void              MinimumYSize(const int y_size)                  { m_minimum_y_size=y_size;                   }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CWindow::CWindow(void) : m_minimum_x_size(0),
                         m_minimum_y_size(0)
  {
...
  }
//+------------------------------------------------------------------+
//| Initialization of the properties                                 |
//+------------------------------------------------------------------+
void CWindow::InitializeProperties(const long chart_id,const int subwin,const string caption_text,const int x_gap,const int y_gap)
  {
...
   m_x_size         =(m_x_size<1)? 200 : m_x_size;
   m_y_size         =(m_y_size<1)? 200 : m_y_size;
...
   m_minimum_x_size =(m_minimum_x_size<200)? m_x_size : m_minimum_x_size;
   m_minimum_y_size =(m_minimum_y_size<200)? m_y_size : m_minimum_y_size;
...
  }

Before resizing the window during a maximization to full screen, it is necessary to store the current dimensions, coordinates and auto-resizing modes, if they had been set. These values are stored in special private fields of the class:

class CWindow : public CElement
  {
private:
   //--- Last coordinates and dimensions of the window before switching to full screen
   int               m_last_x;
   int               m_last_y;
   int               m_last_x_size;
   int               m_last_y_size;
   bool              m_last_auto_xresize;
   bool              m_last_auto_yresize;
  };

The CWindow::OnClickFullScreenButton() method is used for handling the click on the full screen button. It first checks the identifier and index of the control, and then the code is divided into two blocks:

At the end of the CWindow::OnClickFullScreenButton() method, the form is redrawn:

class CWindow : public CElement
  {
private:
   //--- Status of the window in full screen mode
   bool              m_is_fullscreen;
   //---
public:
   //--- Switch to full screen or to previous window size
   bool              OnClickFullScreenButton(const int id=WRONG_VALUE,const int index=WRONG_VALUE);
  };
//+------------------------------------------------------------------+
//| Switch to full screen or to previous form size                   |
//+------------------------------------------------------------------+
bool CWindow::OnClickFullScreenButton(const int id=WRONG_VALUE,const int index=WRONG_VALUE)
  {
//--- Check the identifier and index of control if there was an external call
   int check_id    =(id!=WRONG_VALUE)? id : CElementBase::Id();
   int check_index =(index!=WRONG_VALUE)? index : CElementBase::Index();
//--- Leave, if the indexes do not match
   if(check_id!=m_button_fullscreen.Id() || check_index!=m_button_fullscreen.Index())
      return(false);
//--- If the window is not in full screen
   if(!m_is_fullscreen)
     {
      //--- Switch to full screen
      m_is_fullscreen=true;
      //--- Get the current dimensions of the chart window
      SetWindowProperties();
      //--- Store the current coordinates and dimensions of the form
      m_last_x            =m_x;
      m_last_y            =m_y;
      m_last_x_size       =m_x_size;
      m_last_y_size       =m_full_height;
      m_last_auto_xresize =m_auto_xresize_mode;
      m_last_auto_yresize =m_auto_yresize_mode;
      //--- Enable auto-resizing of the form
      m_auto_xresize_mode=true;
      m_auto_yresize_mode=true;
      //--- Maximize the form to the entire chart
      ChangeWindowWidth(m_chart.WidthInPixels()-2);
      ChangeWindowHeight(m_chart.HeightInPixels(m_subwin)-3);
      //--- Update the location
      m_x=m_y=1;
      Moving(m_x,m_y);
      //--- Replace the button icon
      m_button_fullscreen.IconFile("Images\\EasyAndFastGUI\\Controls\\minimize_to_window.bmp");
      m_button_fullscreen.IconFileLocked("Images\\EasyAndFastGUI\\Controls\\minimize_to_window.bmp");
     }
//--- If the window is in full screen
   else
     {
      //--- Switch to previous window size
      m_is_fullscreen=false;
      //--- Disable auto-resizing
      m_auto_xresize_mode=m_last_auto_xresize;
      m_auto_yresize_mode=m_last_auto_yresize;
      //--- If the mode is disabled, set the previous size
      if(!m_auto_xresize_mode)
         ChangeWindowWidth(m_last_x_size);
      if(!m_auto_yresize_mode)
         ChangeWindowHeight(m_last_y_size);
      //--- Update the location
      m_x=m_last_x;
      m_y=m_last_y;
      Moving(m_x,m_y);
      //--- Replace the button icon
      m_button_fullscreen.IconFile("Images\\EasyAndFastGUI\\Controls\\full_screen.bmp");
      m_button_fullscreen.IconFileLocked("Images\\EasyAndFastGUI\\Controls\\full_screen.bmp");
     }
//--- Remove the focus from the button
   m_button_fullscreen.MouseFocus(false);
   m_button_fullscreen.Update(true);
   return(true);
  }

The CWindow::OnClickFullScreenButton() method is called in the control's event handler when the custom ON_CLICK_BUTTON event arrives. 

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handling the event of clicking the form buttons
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      ...
      //--- Check for full screen mode
      if(OnClickFullScreenButton((uint)lparam,(uint)dparam))
         return;
      ...
      //---
      return;
     }
  }

The result will be as follows:

 Fig. 1. Demonstration of switching to full screen and back.


Fig. 1. Demonstration of switching to full screen and back.


In order to switch to full screen mode and back by double-clicking the window title, it is now sufficient to process this double click event (ON_DOUBLE_CLICK) over the window title in the control's event handler.

void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handling the event of double-clicking an object
   if(id==CHARTEVENT_CUSTOM+ON_DOUBLE_CLICK)
     {
      //--- If the event was generated in the window title
      if(CursorInsideCaption(m_mouse.X(),m_mouse.Y()))
         OnClickFullScreenButton(m_button_fullscreen.Id(),m_button_fullscreen.Index());
      //---
      return;
     }
  }

This is how it works:

 Fig. 2. Demonstration of switching to full screen by double clicking the title.


Fig. 2. Demonstration of switching to full screen by double clicking the title.


Now let us consider the mode of resizing the window by dragging its borders. Use the CWindow::ResizeMode() method to enable it.

class CWindow : public CElement
  {
private:
   //--- Window resizing mode
   bool              m_xy_resize_mode;
   //---
public:
   //--- Ability to resize the window
   bool              ResizeMode(void)                          const { return(m_xy_resize_mode);                  }
   void              ResizeMode(const bool state)                    { m_xy_resize_mode=state;                    }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CWindow::CWindow(void) : m_xy_resize_mode(false)
  {
...
  }

To track the left mouse button click on the window borders, one more identifier (PRESSED_INSIDE_BORDER) is required in the ENUM_MOUSE_STATE enumeration, which is located in the Enums.mqh file.

//+------------------------------------------------------------------+
//|                                                        Enums.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Enumeration of left mouse button holding areas                   |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_STATE
  {
   NOT_PRESSED           =0,
   PRESSED_INSIDE        =1,
   PRESSED_OUTSIDE       =2,
   PRESSED_INSIDE_HEADER =3,
   PRESSED_INSIDE_BORDER =4
  };

 If the resizing mode is enabled, then a graphical object with the new MP_WINDOW_RESIZE identifier from the ENUM_MOUSE_POINTER enumeration is created for the mouse cursor.

//+------------------------------------------------------------------+
//| Enumeration of the pointer types                                 |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_POINTER
  {
   MP_CUSTOM            =0,
   MP_X_RESIZE          =1,
   MP_Y_RESIZE          =2,
   MP_XY1_RESIZE        =3,
   MP_XY2_RESIZE        =4,
   MP_WINDOW_RESIZE     =5,
   MP_X_RESIZE_RELATIVE =6,
   MP_Y_RESIZE_RELATIVE =7,
   MP_X_SCROLL          =8,
   MP_Y_SCROLL          =9,
   MP_TEXT_SELECT       =10
  };

The CreateResizePointer() method has been added to the CWindow class to create the graphical object for the mouse cursor:

class CWindow : public CElement
  {
private:
   bool              CreateResizePointer(void);
  };
//+------------------------------------------------------------------+
//| Creates the mouse cursor for resizing                            |
//+------------------------------------------------------------------+
bool CWindow::CreateResizePointer(void)
  {
//--- Leave, if the resizing mode is disabled
   if(!m_xy_resize_mode)
      return(true);
//--- Properties
   m_xy_resize.XGap(13);
   m_xy_resize.YGap(11);
   m_xy_resize.XSize(23);
   m_xy_resize.YSize(23);
   m_xy_resize.Id(CElementBase::Id());
   m_xy_resize.Type(MP_WINDOW_RESIZE);
//--- Create control
   if(!m_xy_resize.CreatePointer(m_chart_id,m_subwin))
      return(false);
//---
   return(true);
  }

It was necessary to implement several methods to resize the window. Let us consider them in order.

The mouse cursor location must be tracked when it appears within the window area. In this version, the window can be resized by dragging its left, right or bottom border. The CWindow::ResizeModeIndex() method tracks the focus over one of the listed borders and stores the border index for subsequent handling in other methods. The mouse cursor coordinates relative to the window are passed to this method for calculations.

class CWindow : public CElement
  {
private:
   //--- Index of the border for resizing the window
   int               m_resize_mode_index;
   //---
private:
   //--- Returns the index of the window resizing mode
   int               ResizeModeIndex(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Returns the index of the window resizing mode                    |
//+------------------------------------------------------------------+
int CWindow::ResizeModeIndex(const int x,const int y)
  {
//--- Return the border index, if already dragging
   if(m_resize_mode_index!=WRONG_VALUE && m_mouse.LeftButtonState())
      return(m_resize_mode_index);
//--- Width, offset and index of the border
   int width  =5;
   int offset =15;
   int index  =WRONG_VALUE;
//--- Checking the focus on the left border
   if(x>0 && x<width && y>m_caption_height+offset && y<m_y_size-offset)
      index=0;
//--- Checking the focus on the right border
   else if(x>m_x_size-width && x<m_x_size && y>m_caption_height+offset && y<m_y_size-offset)
      index=1;
//--- Checking the focus on the bottom border
   else if(y>m_y_size-width && y<m_y_size && x>offset && x<m_x_size-offset)
      index=2;
//--- If the index is obtained, mark the area of the click
   if(index!=WRONG_VALUE)
      m_clamping_area_mouse=PRESSED_INSIDE_BORDER;
//--- Return the area index
   return(index);
  }

Auxiliary class fields will be required: for determining the capture points, storing the initial dimensions and for subsequent calculations. Once the resizing process starts, it will be necessary to generate a message for forming the list of available controls. Therefore, a method for generating the message to restore the controls and resetting the service fields will also be required: CWindow::ZeroResizeVariables().

class CWindow : public CElement
  {
private:
   //--- Variables related to resizing the window
   int               m_x_fixed;
   int               m_size_fixed;
   int               m_point_fixed;
   //---
private:
   //--- Zeroing variables
   void              ZeroResizeVariables(void);
  };
//+------------------------------------------------------------------+
//| Resetting variables related to resizing the window               |
//+------------------------------------------------------------------+
void CWindow::ZeroResizeVariables(void)
  {
//--- Leave, if already zeroed
   if(m_point_fixed<1)
      return;
//--- Zero
   m_x_fixed     =0;
   m_size_fixed  =0;
   m_point_fixed =0;
//--- Send a message to restore the available controls
   ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),1,"");
//--- Send a message about the change in the graphical interface
   ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,"");
  }

The CWindow::CheckResizePointer() method has been implemented to determine if the mouse cursor showing and hiding modes are ready for resizing the window. Here, the CWindow::ResizeModeIndex() method is used for determining the index of the border. 

If the mouse cursor is not displayed yet, then, at a certain border index, it is necessary to set the corresponding icon, adjust the position and output the pointer.

In case determination of the border index shows that the mouse cursor is already displayed, it will be moved after the mouse cursor, if there is focus over one of the borders. If there is no focus and the left mouse button is released, the cursor is hidden and the variables are zeroed.

The CWindow::CheckResizePointer() method returns true if the window border for resizing is defined, and false otherwise.

class CWindow : public CElement
  {
private:
   //--- Check the readiness to resize the window
   bool              CheckResizePointer(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Check the readiness to resize the window                         |
//+------------------------------------------------------------------+
bool CWindow::CheckResizePointer(const int x,const int y)
  {
//--- Determine the current border index
   m_resize_mode_index=ResizeModeIndex(x,y);
//--- If the cursor is hidden
   if(!m_xy_resize.IsVisible())
     {
      //--- If the border is defined
      if(m_resize_mode_index!=WRONG_VALUE)
        {
         //--- For determining the index of the displayed icon of the mouse pointer
         int index=WRONG_VALUE;
         //--- If on vertical borders
         if(m_resize_mode_index==0 || m_resize_mode_index==1)
            index=0;
         //--- If on horizontal borders
         else if(m_resize_mode_index==2)
            index=1;
         //--- Change the icon
         m_xy_resize.ChangeImage(0,index);
         //--- Move, redraw and show
         m_xy_resize.Moving(m_mouse.X(),m_mouse.Y());
         m_xy_resize.Update(true);
         m_xy_resize.Reset();
         return(true);
        }
     }
   else
     {
      //--- Move the pointer
      if(m_resize_mode_index!=WRONG_VALUE)
         m_xy_resize.Moving(m_mouse.X(),m_mouse.Y());
      //--- Hide the cursor
      else if(!m_mouse.LeftButtonState())
        {
         //--- Hide the pointer and reset the variables
         m_xy_resize.Hide();
         ZeroResizeVariables();
        }
      //--- Refresh the chart
      m_chart.Redraw();
      return(true);
     }
//---
   return(false);
  }

The CWindow::CheckDragWindowBorder() method is used for checking the start of dragging the window border. The moment the border is dragged, it is necessary to store the current dimensions and the coordinate of the initial dragging point in the fields of the class. At the same time, a message to determine the available controls is sent

If the subsequent calls to this method show the border is already being dragged, then it is necessary to calculate the distance passed in this mode and return the resulting value.

class CWindow : public CElement
  {
private:
   //--- Check dragging of the window border
   int               CheckDragWindowBorder(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Check dragging of the window border                              |
//+------------------------------------------------------------------+
int CWindow::CheckDragWindowBorder(const int x,const int y)
  {
//--- To determine the displacement distance
   int distance=0;
//--- If the border is not dragged
   if(m_point_fixed<1)
     {
      //--- If resizing along the X axis
      if(m_resize_mode_index==0 || m_resize_mode_index==1)
        {
         m_x_fixed     =m_x;
         m_size_fixed  =m_x_size;
         m_point_fixed =x;
        }
      //--- If resizing along the Y axis
      else if(m_resize_mode_index==2)
        {
         m_size_fixed  =m_y_size;
         m_point_fixed =y;
        }
      //--- Send a message to determine the available controls
      ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),0,"");
      //--- Send a message about the change in the graphical interface
      ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,"");
      return(0);
     }
//--- If this is the left border
   if(m_resize_mode_index==0)
      distance=m_mouse.X()-m_x_fixed;
//--- If this is the right border
   else if(m_resize_mode_index==1)
      distance=x-m_point_fixed;
//--- If this is the bottom border
   else if(m_resize_mode_index==2)
      distance=y-m_point_fixed;
//--- Return the passed distance
   return(distance);
  }

The result returned by the CWindow::CheckDragWindowBorder() method is passed to the CWindow::CalculateAndResizeWindow() method, where the window coordinates and dimensions are calculated relative to its border

class CWindow : public CElement
  {
private:
   //--- Calculating and resizing the window
   void              CalculateAndResizeWindow(const int distance);
  };
//+------------------------------------------------------------------+
//| Calculating and resizing the window                              |
//+------------------------------------------------------------------+
void CWindow::CalculateAndResizeWindow(const int distance)
  {
//--- Left border
   if(m_resize_mode_index==0)
     {
      int new_x      =m_x_fixed+distance-m_point_fixed;
      int new_x_size =m_size_fixed-distance+m_point_fixed;
      //--- Leave, if exceeding the limits
      if(new_x<1 || new_x_size<=m_minimum_x_size)
         return;
      //--- Coordinates
      CElementBase::X(new_x);
      m_canvas.X_Distance(new_x);
      //--- Set and store the size
      CElementBase::XSize(new_x_size);
      m_canvas.XSize(new_x_size);
      m_canvas.Resize(new_x_size,m_canvas.YSize());
     }
//--- Right border
   else if(m_resize_mode_index==1)
     {
      int gap_x2     =m_chart_width-m_mouse.X()-(m_size_fixed-m_point_fixed);
      int new_x_size =m_size_fixed+distance;
      //--- Leave, if exceeding the limits
      if(gap_x2<1 || new_x_size<=m_minimum_x_size)
         return;
      //--- Set and store the size
      CElementBase::XSize(new_x_size);
      m_canvas.XSize(new_x_size);
      m_canvas.Resize(new_x_size,m_canvas.YSize());
     }
//--- Lower border
   else if(m_resize_mode_index==2)
     {
      int gap_y2=m_chart_height-m_mouse.Y()-(m_size_fixed-m_point_fixed);
      int new_y_size=m_size_fixed+distance;
      //--- Leave, if exceeding the limits
      if(gap_y2<2 || new_y_size<=m_minimum_y_size)
         return;
      //--- Set and store the size
      m_full_height=new_y_size;
      CElementBase::YSize(new_y_size);
      m_canvas.YSize(new_y_size);
      m_canvas.Resize(m_canvas.XSize(),new_y_size);
     }
  }

The CWindow::CheckDragWindowBorder() and CWindow::CheckDragWindowBorder() methods are called within the CWindow::UpdateSize() method. Here, at the beginning of the method, there is a check if the left mouse button is pressed. If the button is released, then all values of variable related to window resizing are reset, and the program leaves the method.

If the left mouse button is pressed, then (1) determine the distance passed by the border in the dragged state, (2) calculate and resize the window, (3) redraw the window and (4) adjust the location of its elements.

At the end of the method, depending on the axis the window was resized on, an event is generated, which will later be used for resizing all controls attached to the window and that have the corresponding mode enabled.

class CWindow : public CElement
  {
private:
   //--- Updating the window sizes
   void              UpdateSize(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Updating the window sizes                                        |
//+------------------------------------------------------------------+
void CWindow::UpdateSize(const int x,const int y)
  {
//--- If finished and the left mouse button is released, reset the values
   if(!m_mouse.LeftButtonState())
     {
      ZeroResizeVariables();
      return;
     }
//--- Leave, if the capture and movement of the border has not started yet
   int distance=0;
   if((distance=CheckDragWindowBorder(x,y))==0)
      return;
//--- Calculating and resizing the window
   CalculateAndResizeWindow(distance);
//--- Redraw the window
   Update(true);
//--- Update the position of objects
   Moving(m_x,m_y);
//--- A message that the window sizes have been changed
   if(m_resize_mode_index==2)
      ::EventChartCustom(m_chart_id,ON_WINDOW_CHANGE_YSIZE,(long)CElementBase::Id(),0,"");
   else
      ::EventChartCustom(m_chart_id,ON_WINDOW_CHANGE_XSIZE,(long)CElementBase::Id(),0,"");
  }

All the listed methods for measuring the window dimensions are called in the main method CWindow::ResizeWindow(). It first checks if the window is available. Then, if the left mouse button was not pressed over one of the window borders, the program leaves the method. Then three more checks follow: (1) if the resizing mode is enabled, (2) if the window is maximized to full screen and (3) not minimized.

If all checks are passed, then the relative mouse cursor coordinates are obtained, and if the window border was captured, the control is resized.

class CWindow : public CElement
  {
private:
   //--- Controls the window sizes
   void              ResizeWindow(void);
  };
//+------------------------------------------------------------------+
//| Controls the window sizes                                        |
//+------------------------------------------------------------------+
void CWindow::ResizeWindow(void)
  {
//--- Leave, if the window is unavailable
   if(!IsAvailable())
      return;
//--- Leave, if the mouse button was not pressed over the form button
   if(m_clamping_area_mouse!=PRESSED_INSIDE_BORDER && m_clamping_area_mouse!=NOT_PRESSED)
      return;
//--- Leave, if (1) the window resizing mode is disabled or 
//    (2) the window is in full screen or (3) the window is minimized
   if(!m_xy_resize_mode || m_is_fullscreen || m_is_minimized)
      return;
//--- Coordinates
   int x =m_mouse.RelativeX(m_canvas);
   int y =m_mouse.RelativeY(m_canvas);
//--- Check readiness to change the width of lists
   if(!CheckResizePointer(x,y))
      return;
//--- Updating the window sizes
   UpdateSize(x,y);
  }

The CWindow::ResizeWindow() method is called in the event handler when an event for mouse cursor movement arrives (CHARTEVENT_MOUSE_MOVE). 

This is how it works:

 Fig. 3. Demonstration of window resizing by moving its borders.


Fig. 3. Demonstration of window resizing by moving its borders.



Text boxes and combo boxes in table cells

If table cells have different controls, the table becomes a very flexible tool for managing the data contained in it. The closest example can be seen right in the MetaTrader trading terminals, on the Input parameters tab in the windows of MQL application settings or on the Parameters tab in the Strategy Tester window. Graphical interfaces with such capabilities will bring MQL applications to a new level.

 Fig. 4. Settings window of an MQL program.


Fig. 4. Settings window of an MQL program.


 Fig. 5. Settings of an MQL application in the strategy tester.


Fig. 5. Settings of an MQL application in the strategy tester.


One of the previous articles added check boxes and buttons to table cells. Now let us consider the implementation of text edit boxes and combo boxes. 

First of all, two new identifiers have been added to the ENUM_TYPE_CELL enumeration in the Enums.mqh file to denote table cell types:

//+------------------------------------------------------------------+
//| Enumeration of the table cell types                              |
//+------------------------------------------------------------------+
enum ENUM_TYPE_CELL
  {
   CELL_SIMPLE   =0,
   CELL_BUTTON   =1,
   CELL_CHECKBOX =2,
   CELL_COMBOBOX =3,
   CELL_EDIT     =4
  };

To implement the planned, it is sufficient to create only one text edit box control (CTextEdit) and/or one combo box control (CComboBox) in the table, as component parts of the CTable control. They will appear on double clicking a cell, when its value is to be modified. 

When setting the cell type using the CTable::CellType() method, it is necessary to set the flag once in the special fields of the class, in case the CELL_EDIT or CELL_COMBOBOX type is specified.

//+------------------------------------------------------------------+
//| Class for creating a rendered table                              |
//+------------------------------------------------------------------+
class CTable : public CElement
  {
private:
   //--- Presence of table cells with text edit boxes and combo boxes
   bool              m_edit_state;
   bool              m_combobox_state;
   //---
public:
   //--- Setting/getting the cell type
   void              CellType(const uint column_index,const uint row_index,const ENUM_TYPE_CELL type);
  };
//+------------------------------------------------------------------+
//| Sets the cell type                                               |
//+------------------------------------------------------------------+
void CTable::CellType(const uint column_index,const uint row_index,const ENUM_TYPE_CELL type)
  {
//--- Checking for exceeding the array range
   if(!CheckOutOfRange(column_index,row_index))
      return;
//--- Set the cell type
   m_columns[column_index].m_rows[row_index].m_type=type;
//--- Sign of a text edit box presence
   if(type==CELL_EDIT && !m_edit_state)
      m_edit_state=true;
//--- Sign of a combo box presence
   else if(type==CELL_COMBOBOX && !m_combobox_state)
      m_combobox_state=true;
  }

When creating a table, if it turns out that no cells of the CELL_EDIT or CELL_COMBOBOX were set, the controls of the corresponding types will not be created. If necessary, the pointers to these controls can be obtained.

class CTable : public CElement
  {
private:
   //--- Objects for creating a table
   CTextEdit         m_edit;
   CComboBox         m_combobox;
   //---
private:
   bool              CreateEdit(void);
   bool              CreateCombobox(void);
   //---
public:
   //--- Returns pointers to controls
   CTextEdit        *GetTextEditPointer(void)                { return(::GetPointer(m_edit));     }
   CComboBox        *GetComboboxPointer(void)                { return(::GetPointer(m_combobox)); }
  };

When handling a double click on the table, the CTable::CheckCellElement() method is called. It includes the appropriate additions to the cells of the CELL_EDIT and CELL_COMBOBOX types. The code listing below shows the shortened version of this method. The methods for handling different cell types will be described in details below.

//+------------------------------------------------------------------+
//| Check if the cell control was activated when clicked             |
//+------------------------------------------------------------------+
bool CTable::CheckCellElement(const int column_index,const int row_index,const bool double_click=false)
  {
//--- Leave, if the cell has no controls
   if(m_columns[column_index].m_rows[row_index].m_type==CELL_SIMPLE)
      return(false);
//---
   switch(m_columns[column_index].m_rows[row_index].m_type)
     {
      ...
      //--- If this is a cell with a text edit box
      case CELL_EDIT :
        {
         if(!CheckPressedEdit(column_index,row_index,double_click))
            return(false);
         //---
         break;
        }
      //--- If this is a cell with a combo box
      case CELL_COMBOBOX :
        {
         if(!CheckPressedCombobox(column_index,row_index,double_click))
            return(false);
         //---
         break;
        }
     }
//---
   return(true);
  }

Before proceeding to the consideration of methods for handling clicks on table cells with controls, let us dwell on the additions to the control of the CTextBox type. Sometimes it is necessary for all the text contained in a text box to be automatically selected when the text box is activated, and for the text cursor to be moved to the end of the line. This is convenient for quickly entering and replacing all text. 

The automatic selection of text in the current version works only for a single-line text box. This mode can be enabled using the CTextBox::AutoSelectionMode() method. 

//+------------------------------------------------------------------+
//| Class for creating a multiline text box                          |
//+------------------------------------------------------------------+
class CTextBox : public CElement
  {
private:
   //--- Automatic text selection mode
   bool              m_auto_selection_mode;
   //---
public:
   //--- Automatic text selection mode
   void              AutoSelectionMode(const bool state)       { m_auto_selection_mode=state;     }
  };

A private method CTextBox::SelectAllText() has been implemented to fully select the text in a text box. Here, the number of characters of the first line is obtained first, and the indexes for text selection are set. Next, the visible text area must be moved all the way to the right. Finally, the text cursor must be moved to the end of the line.

class CTextBox : public CElement
  {
private:
   //--- Select all text
   void              SelectAllText(void);
  };
//+------------------------------------------------------------------+
//| Select all text                                                  |
//+------------------------------------------------------------------+
void CTextBox::SelectAllText(void)
  {
//--- Get the size of the array of characters
   int symbols_total=::ArraySize(m_lines[0].m_symbol);
//--- Set the indexes for selecting text
   m_selected_line_from   =0;
   m_selected_line_to     =0;
   m_selected_symbol_from =0;
   m_selected_symbol_to   =symbols_total;
//--- Move the thumb of the horizontal scrollbar to the last position
   HorizontalScrolling();
//--- Move the cursor to the end of the line
   SetTextCursor(symbols_total,0);
  }

The edit box will appear after double clicking a table cell, but to avoid one more click for activating the text box, an additional public CTextBox::ActivateTextBox() method will be required. A call to it simulates a click on the text box. For this, simply call the CTextBox::OnClickTextBox() method, passing it the name of the graphical object of the control. Selection of text will be carried out in this method.

class CTextBox : public CElement
  {
public:
   //--- Activate the text box
   void              ActivateTextBox(void);
  };
//+------------------------------------------------------------------+
//| Activate the text box                                            |
//+------------------------------------------------------------------+
void CTextBox::ActivateTextBox(void)
  {
   OnClickTextBox(m_textbox.Name());
  }

Since only one text box will be used for the entire table, it is necessary to have the ability to resize it, because the cells may have different widths. Therefore, an additional public CTextBox::ChangeSize() method has been added, which calls the previously implemented methods that have been considered in other articles.

class CTextBox : public CElement
  {
public:
   //--- Resizing
   void              ChangeSize(const uint x_size,const uint y_size);
  };
//+------------------------------------------------------------------+
//| Resizing                                                         |
//+------------------------------------------------------------------+
void CTextBox::ChangeSize(const uint x_size,const uint y_size)
  {
//--- Set the new size
   ChangeMainSize(x_size,y_size);
//--- Calculate the size of the text box
   CalculateTextBoxSize();
//--- Set the new size to the text box
   ChangeTextBoxSize();
  }

Double clicking a cell with a text box calls the CTable::CheckPressedEdit() method. Class fields for storing the indexes of the last edited cell will also be required here in order to process the value input end event (ON_END_EDIT). 

In the current version, the text edit box will only be called by double clicking a cell. Therefore, there is such a check at the beginning of the method. Next, the passed column and row indexes are stored. To place the text edit box above the table cell correctly, it is necessary to calculate the coordinates with consideration of the table offset along the two axes. Additionally, the presence of headers is taken into account in calculations. After that, it is necessary to calculate and set the sizes for the text box, and also insert the current string to be displayed in the cell. Then the text box is activated and made visible, and the chart is redrawn to reflect the latest changes.

class CTable : public CElement
  {
private:
   //--- Indexes of the column and row of the last edited cell
   int               m_last_edit_row_index;
   int               m_last_edit_column_index;
   //---
private:
   //--- Check if the click was on a cell with a text box
   bool              CheckPressedEdit(const int column_index,const int row_index,const bool double_click=false);
  };
//+------------------------------------------------------------------+
//| Check if the click was on the text box in the cell               |
//+------------------------------------------------------------------+
bool CTable::CheckPressedEdit(const int column_index,const int row_index,const bool double_click=false)
  {
//--- Leave, if it is not a double click
   if(!double_click)
      return(false);
//--- Store the indexes
   m_last_edit_row_index    =row_index;
   m_last_edit_column_index =column_index;
//--- Shift along the two axes
   int x_offset=(int)m_table.GetInteger(OBJPROP_XOFFSET);
   int y_offset=(int)m_table.GetInteger(OBJPROP_YOFFSET);
//--- Set the new coordinates
   m_edit.XGap(m_columns[column_index].m_x-x_offset);
   m_edit.YGap(m_rows[row_index].m_y+((m_show_headers)? m_header_y_size : 0)-y_offset);
//--- Size
   int x_size =m_columns[column_index].m_x2-m_columns[column_index].m_x+1;
   int y_size =m_cell_y_size+1;
//--- Set the size
   m_edit.GetTextBoxPointer().ChangeSize(x_size,y_size);
//--- Set the value from the table cell
   m_edit.SetValue(m_columns[column_index].m_rows[row_index].m_full_text);
//--- Activate the text box
   m_edit.GetTextBoxPointer().ActivateTextBox();
//--- Set the focus
   m_edit.GetTextBoxPointer().MouseFocus(true);
//--- Show the text box
   m_edit.Reset();
//--- Redraw the chart
   m_chart.Redraw();
   return(true);
  }

Once the value is entered in the cell, an event with the ON_END_EDIT identifier is generated, which must be received in the event handler of the table. The CTable::OnEndEditCell() method has been implemented to process this event. If there are cells with text boxes present and the identifiers match, then the new value is set to the table cell. After that, the text box must be deactivated and hidden.

class CTable : public CElement
  {
private:
   //--- Handling the end of the value input in the cell
   bool              OnEndEditCell(const int id);
  };
//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   ...
//--- Handling the input end event
   if(id==CHARTEVENT_CUSTOM+ON_END_EDIT)
     {
      if(OnEndEditCell((int)lparam))
         return;
      //---
      return;
     }
   ...
  }
//+------------------------------------------------------------------+
//| Handling the end of the value input in the cell                  |
//+------------------------------------------------------------------+
bool CTable::OnEndEditCell(const int id)
  {
//--- Leave, if (1) the identifiers do not match or (2) there are no cells with text boxes
   if(id!=CElementBase::Id() || !m_edit_state)
      return(false);
//--- Set the value to the table cell
   SetValue(m_last_edit_column_index,m_last_edit_row_index,m_edit.GetValue(),0,true);
   Update();
//--- Deactivate and hide the text box
   m_edit.GetTextBoxPointer().DeactivateTextBox();
   m_edit.Hide();
   m_chart.Redraw();
   return(true);
  }

Clicking outside the activated text box should hide that text box. To do this, the CTable::OnEndEditCell() method will be required. In addition, the text box must be deactivated, so that it is displayed correctly next time it is called. The CTable::OnEndEditCell() method is called in the event handler of the table upon the arrival of the event of changing the state of the left mouse button (ON_CHANGE_MOUSE_LEFT_BUTTON). The same principle is used in the CTable::CheckAndHideCombobox() method for checking if combo boxes are present in cells. The code of this method will not be provided here, as it almost identical to the considered one.

class CTable : public CElement
  {
private:
   //--- Checking if controls in the cells are hidden
   void              CheckAndHideEdit(void);
  };
//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   ...
//--- Change in the state of the left mouse button
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE_MOUSE_LEFT_BUTTON)
     {
      ...
      //--- Checking if text boxes in the cells are hidden
      CheckAndHideEdit();
      //--- Checking if combo boxes in the cells are hidden
      CheckAndHideCombobox();
      return;
     }
   ...
  }
//+------------------------------------------------------------------+
//| Checking if text boxes in the cells are hidden                   |
//+------------------------------------------------------------------+
void CTable::CheckAndHideEdit(void)
  {
//--- Leave, if (1) there is no text box or (2) it is hidden
   if(!m_edit_state || !m_edit.IsVisible())
      return;
//--- Check the focus
   m_edit.GetTextBoxPointer().CheckMouseFocus();
//--- Deactivate and hide the text box, if it (1) is out of focus and (2) the left mouse button is pressed
   if(!m_edit.GetTextBoxPointer().MouseFocus() && m_mouse.LeftButtonState())
     {
      m_edit.GetTextBoxPointer().DeactivateTextBox();
      m_edit.Hide();
      m_chart.Redraw();
     }
  }

Now let us consider how the methods for calling the combo box from a table cell work. For cells of the CELL_COMBOBOX type, an array will be required for storing the values for the combo box list, as well as an additional field for storing the index of the selected item. The array and the field have been added to the CTCell structure.

class CTable : public CElement
  {
private:
   //--- Properties of the table cells
   struct CTCell
     {
      ...
      string            m_value_list[];   // Array of values (for cells with combo boxes)
      int               m_selected_item;  // Selected item in the combo box list
      ...
     };
  };

When the combo box type (CELL_COMBOBOX) is specified for a cell in the custom class before creating the table, it is also necessary to pass the list of values to be passed to the combo box list. 

This is done by the CTable::AddValueList() method. This method is also passed the cell index and index of the item to be selected in the combo box list. The first item is selected by default (index 0). 

A check for exceeding the array range is located at the beginning of the method. After that, the array in the CTCell structure is set the same size as the passed array, and a copy of the values is made. The selected item index is adjusted in case of exceeding the array range, and is also stored in the CTCell structure. The text from the selected item is set to the cell.

class CTable : public CElement
  {
public:
   //--- Add a list of values to the combo box
   void              AddValueList(const uint column_index,const uint row_index,const string &array[],const uint selected_item=0);
  };
//+------------------------------------------------------------------+
//| Add a list of values to the combo box                            |
//+------------------------------------------------------------------+
void CTable::AddValueList(const uint column_index,const uint row_index,const string &array[],const uint selected_item=0)
  {
//--- Checking for exceeding the array range
   if(!CheckOutOfRange(column_index,row_index))
      return;
//--- Set the list size of the specified cell
   uint total=::ArraySize(array);
   ::ArrayResize(m_columns[column_index].m_rows[row_index].m_value_list,total);
//--- Store the passed values
   ::ArrayCopy(m_columns[column_index].m_rows[row_index].m_value_list,array); 
//--- Check the index of the selected item in the list
   uint check_item_index=(selected_item>=total)? total-1 : selected_item;
//--- Store the selected item in the list
   m_columns[column_index].m_rows[row_index].m_selected_item=(int)check_item_index;
//--- Store the text of the selected in the cell
   m_columns[column_index].m_rows[row_index].m_full_text=array[check_item_index];
  }

The CTable::CheckPressedCombobox() method handles double clicking on a cell with a combo box. Here, the cell indexes are first stored for subsequent processing in case a list item is selected. Then the combo box coordinates are set relative to the top left corner of the cell. After that, its controls are set the same sizes as the cell. To resize the button (CButton) and the list (CListView) during runtime, the ChangeSize() method has been added to their classes, as well as two fields. Since the list size may vary from cell to cell, it is necessary to rebuild and refill the list every time. Next, the combo box elements are redrawn, and it is made visible. At the very end of the method, an event regarding changes in the graphical interface is generated

class CTable : public CElement
  {
private:
   //--- Check if the click was on a cell with a combo box
   bool              CheckPressedCombobox(const int column_index,const int row_index,const bool double_click=false);
  };
//+------------------------------------------------------------------+
//| Check if the combo box in the cell was clicked                   |
//+------------------------------------------------------------------+
bool CTable::CheckPressedCombobox(const int column_index,const int row_index,const bool double_click=false)
  {
//--- Leave, if it is not a double click
   if(!double_click)
      return(false);
//--- Store the indexes
   m_last_edit_row_index    =row_index;
   m_last_edit_column_index =column_index;
//--- Shift along the two axes
   int x_offset=(int)m_table.GetInteger(OBJPROP_XOFFSET);
   int y_offset=(int)m_table.GetInteger(OBJPROP_YOFFSET);
//--- Set the new coordinates
   m_combobox.XGap(m_columns[column_index].m_x-x_offset);
   m_combobox.YGap(m_rows[row_index].m_y+((m_show_headers)? m_header_y_size : 0)-y_offset);
//--- Set the button size
   int x_size =m_columns[column_index].m_x2-m_columns[column_index].m_x+1;
   int y_size =m_cell_y_size+1;
   m_combobox.GetButtonPointer().ChangeSize(x_size,y_size);
//--- Set the list size
   y_size=m_combobox.GetListViewPointer().YSize();
   m_combobox.GetListViewPointer().ChangeSize(x_size,y_size);
//--- Set the cell list size
   int total=::ArraySize(m_columns[column_index].m_rows[row_index].m_value_list);
   m_combobox.GetListViewPointer().Rebuilding(total);
//--- Set the list from the cell
   for(int i=0; i<total; i++)
      m_combobox.GetListViewPointer().SetValue(i,m_columns[column_index].m_rows[row_index].m_value_list[i]);
//--- Set the item from the cell
   int index=m_columns[column_index].m_rows[row_index].m_selected_item;
   m_combobox.SelectItem(index);
//--- Update the control
   m_combobox.GetButtonPointer().MouseFocus(true);
   m_combobox.GetButtonPointer().Update(true);
   m_combobox.GetListViewPointer().Update(true);
//--- Show the text box
   m_combobox.Reset();
//--- Redraw the chart
   m_chart.Redraw();
//--- Send a message about the change in the graphical interface
   ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,"");
   return(true);
  }

The event of selecting an item in the combo box list (ON_CLICK_COMBOBOX_ITEM) is handled by the CTable::OnClickComboboxItem() method. Here, it is first checked if the identifiers match and if a combo box is present in the table. If these checks are complete, then the selected item index and the value from the item are set in the cell according to previously stored indexes.

class CTable : public CElement
  {
private:
   //--- Handling the selection of an item in the drop-down list of the cell
   bool              OnClickComboboxItem(const int id);
  };
//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   ...
//--- Handling the event of selecting an item in the list
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
     {
      if(OnClickComboboxItem((int)lparam))
         return;
      //---
      return;
     }
   ...
  }
//+------------------------------------------------------------------+
//| Handling selection of an item in the cell combo box              |
//+------------------------------------------------------------------+
bool CTable::OnClickComboboxItem(const int id)
  {
//--- Leave, if (1) the identifiers do not match or (2) there are no cells with combo boxes
   if(id!=CElementBase::Id() || !m_combobox_state)
      return(false);
//--- Indexes of the last edited cell
   int c=m_last_edit_column_index;
   int r=m_last_edit_row_index;
//--- Store the index of the item selected in the cell
   m_columns[c].m_rows[r].m_selected_item=m_combobox.GetListViewPointer().SelectedItemIndex();
//--- Set the value to the table cell
   SetValue(c,r,m_combobox.GetValue(),0,true);
   Update();
   return(true);
  }

In the end, everything will look like this:

Fig. 6. Demonstration of working with text boxes and combo boxes in table cells. 

Fig. 6. Demonstration of working with text boxes and combo boxes in table cells.



Application for testing

For testing purposes, an MQL application has been created, which contains the table (CTable) and multiline text box (CTextBox) controls. In the first column of the table, all cells contain the checkbox control (CELL_CHECKBOX). In the second column of the table, cells have the "text box" type (CELL_EDIT). In the third column, the cells are alternately set to types "combo box" (CELL_COMBOBOX) and "text box" (CELL_EDIT). In the fifth column, the cells have the "button" type (CELL_BUTTON). The event handler of the MQL application's custom class will process and output the events into the multiline text box. 

This is how it works:

 Fig. 7. MQL application for testing the work done.


Fig. 7. MQL application for testing the work done.


This application is available in the archive attached at the end of the article for a more detailed study.


Conclusion

The table now has the ability to create cells of "text box" and "combo box" types. The form for controls can now be expanded to full screen or resized manually by dragging its borders.

The general scheme of the library at the current stage of development:

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


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


The presented library code is free. You can use it in your projects, including commercial ones, write articles and fulfill orders.

If you have questions about using the material from the article, you can ask them in the comments.