Graphical Interfaces X: Updates for Easy And Fast Library (Build 3)

Anatoli Kazharski | 17 October, 2016


Contents


Introduction

The first article Graphical Interfaces I: Preparation of the Library Structure (Chapter 1) considers in detail what this library is for. A complete list of links to the articles of the first part is at the end of each chapter. There, you can also download a complete version of the library at the current stage of development. The files must be placed in the same directories as they are located in the archive.

The next version of the Easy And Fast library (version 3) is presented in this article. Fixed certain flaws and added new features. More details further in the article.

Starting from this version, the library will only be developed for the MetaTrader 5 platform. This is due to certain fundamental architectural differences and limitations in the MetaTrader 4. However, if there is an urgent need for support of the library for an earlier version of the platform, it is possible to issue a request in the Freelance service addressed to the author or any other developer, willing and able to do the job. 

 

Updates

1. Until now, the MQL applications in the previous articles demonstrated how to implement a graphical interface in the indicator subwindow. Of course, this approach is sufficient for some simple applications. But it would also be good to have the ability to create a graphical interface in a subwindow for an "Expert" type program. That way, it would be possible to create fully fledged trading panels, completely separated from the main chart window. Working in this mode will be easier: the price chart and any important data on the chart would always remain open and uncluttered by the graphical interface of the application. Implementation of this idea within the Easy And Fast library will be described further.

The task was to make the "Expert in subwindow" mode be activated by including the resource with a placeholder indicator (SubWindow.ex5) in the main file of the MQL application. The placeholder is just a subwindow without any series calculations. But since there is currently no way to know if the indicator is included as a resource using the MQL means, temporarily add the directive with the EXPERT_IN_SUBWINDOW constant identifier to the Defines.mqh file. It is required to eliminate the logging of error that occurs when trying to get the handle of an indicator that is not available on the specified directory.

The default value is false, which means that the mode is disabled and the graphical interface will be created in the main chart window (see the code below).

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//--- "Expert in subwindow" mode
#define EXPERT_IN_SUBWINDOW false
...

If it is necessary to create the graphical interface in a subwindow, set the value to true and include the resource with the placeholder indicator to the mail file of the MQL application

//+------------------------------------------------------------------+
//|                                                TestLibrary01.mq5 |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2016, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
//--- Including indicator for the "Expert in subwindow" mode
#resource \\Indicators\\SubWindow.ex5
...

Next, consider the SubWindow.ex5 indicator and how it is linked to the EA. The full code of the SubWindow.ex5 indicator has been provided in the listing below. The program properties specify that the indicator is a subwindow of the chart, the number of buffers and series is zero, the maximum and minimum of the vertical scale are also zero. 

The indicator and expert will exchange messages. The thing is, the indicator "does not know" which modes for plotting the graphical interface are selected in the expert. It needs to be passed a message, if developer had decided to make the subwindow have a fixed height, as well as the height values in pixels. Another ON_SUBWINDOW_CHANGE_HEIGHT event identifier will be required for creating the message. This identifier should also be placed in the Defines.mqh file as well, so that it would be available in applications, which use this library for creating graphical interfaces. 

In order to understand when the EA needs to generate the event for changing the subwindow height: after a successful initialization of the indicator, during the first automatic call to the ::OnCalculate() function (when the prev_calculated argument is zero), the message for the EA will be passed. To unambiguously determine that the message came from the SubWindow.ex5 indicator, apart from the ON_SUBWINDOW_CHANGE_HEIGHT event identifier, the name of the program will be sent as a string parameter (sparam). The message will be traced in the handler of the CWindow class. If all conditions are met there, the indicator will be sent a response with the same (1) identifier(ON_SUBWINDOW_CHANGE_HEIGHT), (2) height value in long parameter of the event(lparam) and (3) the expert name. Once this message is received, it will be processed in the ::OnChartEvent() function. After checking the name, height of the subwindow is set according to the passed value.  

//+------------------------------------------------------------------+
//|                                                    SubWindow.mq5 |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2016, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property indicator_separate_window
#property indicator_plots   0
#property indicator_buffers 0
#property indicator_minimum 0.0
#property indicator_maximum 0.0
//--- Program name
#define PROGRAM_NAME ::MQLInfoString(MQL_PROGRAM_NAME)
//--- Identifier of the event for changing the expert subwindow height
#define ON_SUBWINDOW_CHANGE_HEIGHT (25)
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Short name of the indicator
   ::IndicatorSetString(INDICATOR_SHORTNAME,PROGRAM_NAME);
//--- Initialization successful
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialization                                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int    rates_total,
                const int    prev_calculated,
                const int    begin,
                const double &price[])
  {
//--- If the initialization succeeded
   if(prev_calculated<1)
      //--- Send the message to the expert to get the subwindow size from it
      ::EventChartCustom(0,ON_SUBWINDOW_CHANGE_HEIGHT,0,0.0,PROGRAM_NAME);
u//---
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int    id,
                  const long   &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Handling the event of changing the expert subwindow height
   if(id==CHARTEVENT_CUSTOM+ON_SUBWINDOW_CHANGE_HEIGHT)
     {
      //--- Accept messages only from the expert name
      if(sparam==PROGRAM_NAME)
         return;
      //--- Change the subwindow height
      ::IndicatorSetInteger(INDICATOR_HEIGHT,(int)lparam);
     }
  }
//+------------------------------------------------------------------+

A block of code should be inserted to the event handler of the CWindow class, as shown in the listing below. Once an event with the ON_SUBWINDOW_CHANGE_HEIGHT identifier arrives, multiple checks will need to be passed. The program leaves the method if:

  • the message was sent by the expert for the indicator;
  • this program is not an expert;
  • the mode of the expert subwindow fixed height is not set. 
//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Handling the event of changing the expert subwindow height
   if(id==CHARTEVENT_CUSTOM+ON_SUBWINDOW_CHANGE_HEIGHT)
     {
      //--- Leave, if the message was from the expert
      if(sparam==PROGRAM_NAME)
         return;
      //--- Leave, if this program is not the expert
      if(CElement::ProgramType()!=PROGRAM_EXPERT)
         return;
      //--- Leave, if the mode of the sub-window fixed height is not set
      if(!m_height_subwindow_mode)
         return;
      //--- Calculate and change the subwindow height
      m_subwindow_height=(m_is_minimized)? m_caption_height+3 : m_bg_full_height+3;
      ChangeSubwindowHeight(m_subwindow_height);
      return;
     }
  }

If all checks are passed, then the height for the subwindow is calculated with the consideration of the current state of the window (minimized/maximized). After that, the CWindow::ChangeSubwindowHeight() method is called, which had also been slightly modified (see the code below). Its purpose is that, if the program type is «Expert», then a message is generated for the SubWindow.ex5 indicator

//+------------------------------------------------------------------+
//| Changes the height of the indicator sub-window                   |
//+------------------------------------------------------------------+
void CWindow::ChangeSubwindowHeight(const int height)
  {
//--- If the graphical interface is not in subwindow or the program is of "Script" type
   if(CElement::m_subwin<=0 || CElement::m_program_type==PROGRAM_SCRIPT)
      return;
//--- If the subwindow height needs to be changed
   if(height>0)
     {
      //--- If the program is of the indicator type
      if(CElement::m_program_type==PROGRAM_INDICATOR)
        {
         if(!::IndicatorSetInteger(INDICATOR_HEIGHT,height))
            ::Print(__FUNCTION__," > Failed to change the height of indicator subwindow! Error code: ",::GetLastError());
        }
      //--- If the program is of the "Expert" type
      else
        {
         //--- Send the message to the SubWindow.ex5 indicator, informing that the window sizes must be changed
         ::EventChartCustom(m_chart_id,ON_SUBWINDOW_CHANGE_HEIGHT,(long)height,0,PROGRAM_NAME);
        }
     }
  }

The engine of the library (the CWndEvents class) also requires certain additions to be made in order to determine, check and adjust the subwindow number, where the graphical interface of the MQL application of the "Expert" type is to be placed. The code for the "Expert in subwindow" mode should be added to the CWndEvents::DetermineSubwindow() method. The code listing below shows the shortened version of this method. Entry to the block is performed on the condition that the program type is "Expert". Next comes the check of whether the "Expert in subwindow" mode is enabled. If enabled, get the handle of the SubWindow.ex5 indicator. And if there were no problems with that, then the current number of subwindows in the chart window is determined first. The obtained value defines the number of the SubWindow.ex5 indicator subwindow, which is set by the ::ChartIndicatorAdd() function. Then, if there were no errors when setting the subwindow, it stores (1) the number of expert subwindow, (2) the current number of subwindows and (3) the short name of the SubWindow.ex5 indicator.  

//+------------------------------------------------------------------+
//| Class for event handling                                         |
//+------------------------------------------------------------------+
class CWndEvents : public CWndContainer
  {
protected:
   //--- Handle of the expert subwindow
   int               m_subwindow_handle;
   //--- Name of the expert subwindow
   string            m_subwindow_shortname;
   //--- The number of subwindows on the chart after setting the expert subwindow
   int               m_subwindows_total;
  };
//+------------------------------------------------------------------+
//| Identifying the sub-window number                                |
//+------------------------------------------------------------------+
void CWndEvents::DetermineSubwindow(void)
  {
//--- Leave, if the program type is "Script"
//--- Reset the last error

//--- If the program type is "Expert"
   if(PROGRAM_TYPE==PROGRAM_EXPERT)
     {
      //--- Leave, if the graphical interface of the expert is required in the main window
      if(!EXPERT_IN_SUBWINDOW)
         return;
      //--- Get the handle of the placeholder indicator (empty subwindow)
      m_subwindow_handle=iCustom(Symbol(),Period(),"::Indicators\\SubWindow.ex5");
      //--- If there is no such indicator, report the error to the log
      if(m_subwindow_handle==INVALID_HANDLE)
         ::Print(__FUNCTION__," > Error getting the indicator handle in the directory ::Indicators\\SubWindow.ex5 !");
      //--- If the handle is obtained, then the indicator exists, included in the application as a resource,
      //    and this means that the graphical interface of the application must be placed in the subwindow.
      else
        {
         //--- Get the number of subwindows on the chart
         int subwindows_total=(int)::ChartGetInteger(m_chart_id,CHART_WINDOWS_TOTAL);
         //--- Set the subwindow for the graphical interface of the expert
         if(::ChartIndicatorAdd(m_chart_id,subwindows_total,m_subwindow_handle))
           {
            //--- Store the subwindow number and the current number of subwindows on the chart
            m_subwin           =subwindows_total;
            m_subwindows_total =subwindows_total+1;
            //--- Get and store the short name of the expert subwindow
            m_subwindow_shortname=::ChartIndicatorName(m_chart_id,m_subwin,0);
           }
         //--- If the subwindow was not set
         else
            ::Print(__FUNCTION__," > Error setting the expert subwindow! Error code: ",::GetLastError());
        }
      u//---
      return;
     }
//--- Identifying the indicator window
//--- Leave, if failed to identify the number
//--- If this is not the main window of the chart
...
  }

It is also necessary to make sure than the number of the expert subwindow gets adjusted for correct operation of its graphical interface when adding and removing other indicators in the chart subwindows. In addition, let us make it so that if the user deletes the expert subwindow, the expert gets removed as well, leaving a log message in the "Experts" tab of the "Toolbox" window, indicating the reason for removal from the chart. The CWndEvents::CheckExpertSubwindowNumber() method has been implemented for that purpose. Admission to this method is carried out on the condition that the program has a type of "Expert". If the check is passed, then the number of subwindows in the chart window will be calculated. If it turns out that the number of subwindows had not changed, the program leaves the method. 

Then it is necessary to find the expert subwindow in a cycle, and if it exists — check if its subwindow number had changed. The subwindow number could have been changed due to adding or removing an indicator which was also located in its separate subwindow. If the subwindow number has changed, it is necessary to store its number and update the value in all controls of the main window

If the subwindow is not found, there can be only one reason: it has been deleted. In this case, the expert will also be removed from the chart

class CWndEvents : public CWndContainer
  {
private:
   //--- Check and update the expert subwindow number
   void              CheckExpertSubwindowNumber(void);
  };
//+------------------------------------------------------------------+
//| Check and update the expert subwindow number                     |
//+------------------------------------------------------------------+
void CWndEvents::CheckExpertSubwindowNumber(void)
  {
//--- Leave, if this is not expert
   if(PROGRAM_TYPE!=PROGRAM_EXPERT)
      return;
//--- Get the number of subwindows on the chart
   int subwindows_total=(int)::ChartGetInteger(m_chart_id,CHART_WINDOWS_TOTAL);
//--- Leave, if the number of subwindows and the number of indicators have not changed
   if(subwindows_total==m_subwindows_total)
      return;
//--- Store the current number of subwindows
   m_subwindows_total=subwindows_total;
//--- For checking if expert subwindow is present
   bool is_subwindow=false;
//--- find the expert subwindow
   for(int sw=0; sw<subwindows_total; sw++)
     {
      //--- Stop the cycle, if the expert subwindow has been found
      if(is_subwindow)
         break;
      //--- The number of indicators in this window/subwindow
      int indicators_total=::ChartIndicatorsTotal(m_chart_id,sw);
      //--- Iterate over all indicators in the window 
      for(int i=0; i<indicators_total; i++)
        {
         //--- Get the short name of the indicator
         string indicator_name=::ChartIndicatorName(m_chart_id,sw,i);
         //--- If this is not the expert subwindow, go to the next
         if(indicator_name!=m_subwindow_shortname)
            continue;
         //--- Mark that expert has a subwindow
         is_subwindow=true;
         //--- If the subwindow number has changed, then 
         //    it is necessary to store the new number in all controls of the main form
         if(sw!=m_subwin)
           {
            //--- Store the subwindow number
            m_subwin=sw;
            //--- Store it in all the controls of the main form of the interface
            int elements_total=CWndContainer::ElementsTotal(0);
            for(int e=0; e<elements_total; e++)
               m_wnd[0].m_elements[e].SubwindowNumber(m_subwin);
           }
         u//---
         break;
        }
     }
//--- If the expert subwindow was not found, remove the expert
   if(!is_subwindow)
     {
      ::Print(__FUNCTION__," > Deleting expert subwindow causes the expert to be removed!");
      //--- Removing the EA from the chart
      ::ExpertRemove();
     }
  }

 

2. The previous version of the library has introduced the ability to toggle automatic change of the form width. Let us add a similar feature for height. The ON_WINDOW_CHANGE_SIZE identifier for the event of changing the form size is not suitable for solving this task, as changing the width and height will be handled as separate events. Therefore, the Defines.mqh file will have two separate identifiers instead of the ON_WINDOW_CHANGE_SIZE, as shown in the code listing below. The corresponding replacement of the identifier has been performed in other library files.  

#define ON_WINDOW_CHANGE_XSIZE     (3)  // Change in the window size along the X axis
#define ON_WINDOW_CHANGE_YSIZE     (4)  // Change in the window size along the Y axis

The CWindow::ChangeWindowHeight() method has been added for changing the form height. When calling the method after changing the form sizes, the message about the performed action is generated at the very end. 

//+------------------------------------------------------------------+
//| Class for creating a form for controls                           |
//+------------------------------------------------------------------+
class CWindow : public CElement
  {
public:
   //--- Managing the size
   void              ChangeWindowHeight(const int height);
  };
//+------------------------------------------------------------------+
//| Changes the height of the window                                 |
//+------------------------------------------------------------------+
void CWindow::ChangeWindowHeight(const int height)
  {
//--- If the height has not changed, leave
   if(height==m_bg.YSize())
      return;
//--- Leave, if the window is minimized
   if(m_is_minimized)
      return;
//--- Update the height for background
   CElement::YSize(height);
   m_bg.YSize(height);
   m_bg.Y_Size(height);
   m_bg_full_height=height;
//--- A message that the window sizes have been changed
   ::EventChartCustom(m_chart_id,ON_WINDOW_CHANGE_YSIZE,(long)CElement::Id(),0,"");
  }

For the form height to change automatically, user developing an MQL application should set the corresponding mode using the CElement::AutoYResizeMode() method: 

//+------------------------------------------------------------------+
//| Base control class                                               |
//+------------------------------------------------------------------+
class CElement
  {
protected:
   //--- Mode of automatic control resizing
   bool              m_auto_yresize_mode;
   u//---
public:
   //--- (1) Mode of automatic control height changing
   bool              AutoYResizeMode(void)                     const { return(m_auto_yresize_mode);          }
   void              AutoYResizeMode(const bool flag)                { m_auto_yresize_mode=flag;             }
  };

Now, if the form autoresizing modes are enabled, then if the chart window size changes, the event handler of form adjusts the form sizes based on the CHARTEVENT_CHART_CHANGE event. This will work both in the indicator subwindow and in the chart window. It is possible to enable any one of the modes, or both at the same time. 

//--- The chart properties change event
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- If the button is unpressed
      if(m_clamping_area_mouse==NOT_PRESSED)
        {
         //--- Get the chart window size
         SetWindowProperties();
         //--- Adjustment of coordinates
         UpdateWindowXY(m_x,m_y);
        }
      //--- Change the width if the mode is enabled
      if(CElement::AutoXResizeMode())
         ChangeWindowWidth(m_chart.WidthInPixels()-2);
      //--- Change the height if the mode is enabled
      if(CElement::AutoYResizeMode())
         ChangeWindowHeight(m_chart.HeightInPixels(m_subwin)-3);
      u//---
      return;
     }

 

3. The mode for automatic height changing also applies to all controls of the library. But in the current version, this will work only in controls listed below:

  • CTabs – simple tabs.
  • CIconTabs – tabs with icons.
  • CCanvasTable – rendered table.
  • CLineGraph – linear chart.

Similar to the earlier addition of the CElement::ChangeWidthByRightWindowSide() virtual method for changing the width of the control to the CElement class, let us add the corresponding method for automatic height changing. In addition, let us create the methods for setting the offset from the lower edge of the form, similar the previously added offsets from the right edge of the form when changing its width. 

class CElement
  {
protected:
   //--- Offset from the right/bottom edge of the form in the mode of automatic control width/height changing
   int               m_auto_xresize_right_offset;
   int               m_auto_yresize_bottom_offset;
   u//---
public:
   //--- Getting/setting the offset from the bottom edge of the form
   int               AutoYResizeBottomOffset(void)             const { return(m_auto_yresize_bottom_offset); }
   void              AutoYResizeBottomOffset(const int offset)       { m_auto_yresize_bottom_offset=offset;  }
   u//---
public:
   //--- Change the height at the bottom edge of the window
   virtual void      ChangeHeightByBottomWindowSide(void) {}
  };

Every control will have its own implementation of the ChangeWidthByRightWindowSide() method, which considers the unique features and modes. As an example, the listing below shows the code of this method in the CCanvasTable class: 

//+------------------------------------------------------------------+
//| Change the height at the bottom edge of the window               |
//+------------------------------------------------------------------+
void CCanvasTable::ChangeHeightByBottomWindowSide(void)
  {
//--- Leave, if the anchoring mode to the bottom of the form is enabled  
   if(m_anchor_bottom_window_side)
     return;
//--- Coordinates
   int y=0;
//--- Size
   int x_size=(m_auto_xresize_mode)? m_wnd.X2()-m_area.X()-m_auto_xresize_right_offset : m_x_size;
   int y_size=m_wnd.Y2()-m_area.Y()-m_auto_yresize_bottom_offset;
//--- Leave, if the size is less than specified
   if(y_size<60)
     return;
//--- Set the new size of the table background
   ChangeMainSize(x_size,y_size);
//--- Calculate the table sizes
   CalculateTableSize();
//--- Check for presence of a scrollbar
   bool is_scrollh=!(m_table_visible_x_size>=m_table_x_size);
   bool is_scrollv=!(m_table_visible_y_size>=m_table_y_size);
//--- Offset relative to the presence of the scrollbar
   int offset=(is_scrollh || (!is_scrollh && !is_scrollv))? 0 : 2;
//--- Calculate and set the new coordinate for the horizontal scrollbar
   y=m_area.Y2()-m_scrollh.ScrollWidth();
   m_scrollh.YDistance(y);
//--- Initialize the horizontal scrollbar for the new size
   m_scrollh.Reinit(m_table_x_size,m_table_visible_x_size);
//--- Calculate and change the height of the vertical scrollbar
   m_scrollv.Reinit(m_table_y_size,m_table_visible_y_size-offset);
   m_scrollv.ChangeYSize((is_scrollh)? m_table_visible_y_size+2 : m_table_visible_y_size);
//--- If the vertical scrollbar is not required
   if(!is_scrollv)
     {
      //--- Hide the vertical scrollbar
      m_scrollv.Hide();
      //--- Change the width of the horizontal scrollbar
      m_scrollh.ChangeXSize(m_area.XSize());
     }
   else
     {
      //--- Show the vertical scrollbar
      if(CElement::IsVisible())
         m_scrollv.Show();
      //--- Calculate and change the width of the horizontal scrollbar
      m_scrollh.ChangeXSize(m_area.XSize()-m_scrollv.ScrollWidth()+1);
     }
//--- Manage the visibility of the horizontal scrollbar
   if(CElement::IsVisible())
     {
      if(!is_scrollh) m_scrollh.Hide();
      else m_scrollh.Show();
     }
//--- Resize the table
   ChangeTableSize(m_table_x_size,m_table_y_size,m_table_visible_x_size,m_table_visible_y_size-offset);
//--- Draw the table
   DrawTable();
//--- Update the position of objects
   Moving(m_wnd.X(),m_wnd.Y());
  }

For all this to work, certain additions and changes should be made to the CWndEvents class. Since the resizing events (width and height) are now generated with unique identifiers, two separate methods are required for their handling. 

//+------------------------------------------------------------------+
//| Class for event handling                                         |
//+------------------------------------------------------------------+
class CWndEvents : public CWndContainer
  {
private:
   //--- Handle changing the window sizes
   bool              OnWindowChangeXSize(void);
   bool              OnWindowChangeYSize(void);
  };
//+------------------------------------------------------------------+
//| ON_WINDOW_CHANGE_XSIZE event                                     |
//+------------------------------------------------------------------+
bool CWndEvents::OnWindowChangeXSize(void)
  {
//--- If the signal is to "Resize the controls"
   if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_CHANGE_XSIZE)
      return(false);
//--- Index of the active window
   int awi=m_active_window_index;
//--- If the window identifiers match
   if(m_lparam!=m_windows[awi].Id())
      return(true);
//--- Change the width of all controls except the form
   int elements_total=CWndContainer::ElementsTotal(awi);
   for(int e=0; e<elements_total; e++)
     {
      //--- If it is a window, go to the next
      if(m_wnd[awi].m_elements[e].ClassName()=="CWindow")
         continue;
      //--- If the mode is enabled, adjust the width
      if(m_wnd[awi].m_elements[e].AutoXResizeMode())
         m_wnd[awi].m_elements[e].ChangeWidthByRightWindowSide();
     }
u//---
   return(true);
  }
//+------------------------------------------------------------------+
//| ON_WINDOW_CHANGE_YSIZE event                                     |
//+------------------------------------------------------------------+
bool CWndEvents::OnWindowChangeYSize(void)
  {
//--- If the signal is to "Resize the controls"
   if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_CHANGE_YSIZE)
      return(false);
//--- Index of the active window
   int awi=m_active_window_index;
//--- If the window identifiers match
   if(m_lparam!=m_windows[awi].Id())
      return(true);
//--- Change the width of all controls except the form
   int elements_total=CWndContainer::ElementsTotal(awi);
   for(int e=0; e<elements_total; e++)
     {
      //--- If it is a window, go to the next
      if(m_wnd[awi].m_elements[e].ClassName()=="CWindow")
         continue;
      //--- If the mode is enabled, adjust the height
      if(m_wnd[awi].m_elements[e].AutoYResizeMode())
         m_wnd[awi].m_elements[e].ChangeHeightByBottomWindowSide();
     }
u//---
   return(true);
  }

The CWndEvents::OnWindowChangeXSize() and CWndEvents::OnWindowChangeYSize() methods are called in the common CWndEvents::ChartEventCustom() method for handling custom events. The code listing below shows the shortened version of this method. 

//+------------------------------------------------------------------+
//| CHARTEVENT_CUSTOM event                                          |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- If the signal is to minimize the form
//--- If the signal is to maximize the form

//--- If the signal is to resize the controls along the X axis
   if(OnWindowChangeXSize())
      return;
//--- If the signal is to resize the controls along the Y axis
   if(OnWindowChangeYSize())
      return;

//--- If the signal is to hide the context menus below the initiating menu item
//--- If the signal is to hide all context menus

//--- If the signal is to open a dialog window
//--- If the signal is to close a dialog window
//--- If the signal is to zero the colors of all elements on the specified form
//--- If the signal is to reset the priorities of the left mouse button click
//--- If the signal is to restore the priorities of the left mouse button click
  }

Now, when resizing the chart window, if the mode of resizing the form and controls specified in it is enabled, they will also be automatically resized. 

The screenshots below show an example of a graphical interface in an MQL application, that was created in the main chart window. For the application window (CWindow), the modes for automatic width and height adjustment to the chart window are set. For the «Main menu» (CMenuBar) and «Status bar» (CStatusBar) controls, the mode of automatic width and height adjustment to the MQL application window is set. For the «Tabs» (CTabs) and «Rendered table» (CCanvasTable) controls, the mode of automatic width and height adjustment to form size is set and the offsets from the bottom edge of the MQL application are specified.

Fig. 1. Minimum size of the terminal window. Graphical interface of an MQL application with automatic resizing modes enabled. 

Fig. 1. Minimum size of the terminal window. Graphical interface of an MQL application with automatic resizing modes enabled.


If the terminal window size changes, when the aforementioned modes are enabled, the graphical interface of the MQL application will also be resized accordingly.

Fig. 2. When the terminal window sizes change, the graphical interface of the MQL application will also ne resized. 

Fig. 2. When the terminal window sizes change, the graphical interface of the MQL application will also ne resized.


4. Often, when designing the graphical interface with variable form sizes, it may be necessary for the controls to be positioned at the right or bottom part of the application window. Previously, there was an option to simply specify the coordinates for the control relative to the top left point of the form it was anchored to. Now, there are four options for positioning the control relative to the form:

  • Top left.
  • Top right.
  • Bottom right.
  • Bottom left.

In order to implement this idea, let us add two methods for setting/getting the modes of control positioning in the right and bottom of the form to the base class of controls (CElement): 

class CElement
  {
protected:
   //--- Anchor points of the control in the right and bottom of the window
   bool              m_anchor_right_window_side;
   bool              m_anchor_bottom_window_side;
   u//---
public:
   //--- Mode (getting/setting) of control anchor point to the (1) right and (2) bottom edge of the window
   bool              AnchorRightWindowSide(void)               const { return(m_anchor_right_window_side);   }
   void              AnchorRightWindowSide(const bool flag)          { m_anchor_right_window_side=flag;      }
   bool              AnchorBottomWindowSide(void)              const { return(m_anchor_bottom_window_side);  }
   void              AnchorBottomWindowSide(const bool flag)         { m_anchor_bottom_window_side=flag;     }
  };

For everything to work according to these modes, changes have been made to all classes of the library controls. As an example, here is the code of the method for creating a text label for the «Checkbox» (CCheckBox) control. Pay attention to the lines that calculate the coordinates and offsets for the graphical object. 

//+------------------------------------------------------------------+
//| Creates the text label of the checkbox                           |
//+------------------------------------------------------------------+
bool CCheckBox::CreateLabel(void)
  {
//--- Forming the object name
   string name=CElement::ProgramName()+"_checkbox_lable_"+(string)CElement::Id();
//--- Coordinates
   int x =(m_anchor_right_window_side)? m_x-m_label_x_gap : m_x+m_label_x_gap;
   int y =(m_anchor_bottom_window_side)? m_y-m_label_y_gap : m_y+m_label_y_gap;
//--- Text color according to the state
   color label_color=(m_check_button_state) ? m_label_color : m_label_color_off;
//--- Set the object
   if(!m_label.Create(m_chart_id,name,m_subwin,x,y))
      return(false);
//--- set properties
   m_label.Description(m_label_text);
   m_label.Font(FONT);
   m_label.FontSize(FONT_SIZE);
   m_label.Color(label_color);
   m_label.Corner(m_corner);
   m_label.Anchor(m_anchor);
   m_label.Selectable(false);
   m_label.Z_Order(m_zorder);
   m_label.Tooltip("\n");
//--- Margins from the edge
   m_label.XGap((m_anchor_right_window_side)? x : x-m_wnd.X());
   m_label.YGap((m_anchor_bottom_window_side)? y : y-m_wnd.Y());
//--- Initializing gradient array
   CElement::InitColorArray(label_color,m_label_color_hover,m_label_color_array);
//--- Store the object pointer
   CElement::AddToArray(m_label);
   return(true);
  }

The Moving() methods of all controls in the library now consider the control positioning modes. As an example, the listing below shows the code of the CCheckBox::Moving() method: 

//+------------------------------------------------------------------+
//| Moving controls                                                  |
//+------------------------------------------------------------------+
void CCheckBox::Moving(const int x,const int y)
  {
//--- Leave, if the element is hidden
   if(!CElement::IsVisible())
      return;
//--- If the anchored to the right
   if(m_anchor_right_window_side)
     {
      //--- Storing coordinates in the element fields
      CElement::X(m_wnd.X2()-XGap());
      //--- Storing coordinates in the fields of the objects
      m_area.X(m_wnd.X2()-m_area.XGap());
      m_check.X(m_wnd.X2()-m_check.XGap());
      m_label.X(m_wnd.X2()-m_label.XGap());
     }
   else
     {
      //--- Storing coordinates in the fields of the objects
      CElement::X(x+XGap());
      //--- Storing coordinates in the fields of the objects
      m_area.X(x+m_area.XGap());
      m_check.X(x+m_check.XGap());
      m_label.X(x+m_label.XGap());
     }
//--- If the anchored to the bottom
   if(m_anchor_bottom_window_side)
     {
      //--- Storing coordinates in the element fields
      CElement::Y(m_wnd.Y2()-YGap());
      //--- Storing coordinates in the fields of the objects
      m_area.Y(m_wnd.Y2()-m_area.YGap());
      m_check.Y(m_wnd.Y2()-m_check.YGap());
      m_label.Y(m_wnd.Y2()-m_label.YGap());
     }
   else
     {
      //--- Storing coordinates in the fields of the objects
      CElement::Y(y+YGap());
      //--- Storing coordinates in the fields of the objects
      m_area.Y(y+m_area.YGap());
      m_check.Y(y+m_check.YGap());
      m_label.Y(y+m_label.YGap());
     }
//--- Updating coordinates of graphical objects
   m_area.X_Distance(m_area.X());
   m_area.Y_Distance(m_area.Y());
   m_check.X_Distance(m_check.X());
   m_check.Y_Distance(m_check.Y());
   m_label.X_Distance(m_label.X());
   m_label.Y_Distance(m_label.Y());
  }

For clarity, the figure below schematically shows all possible combinations of the positioning modes and automatic resizing of controls. It is an abstract example, in which the form (see the ninth column «Result») is represented by a white rectangle with a black bold frame and size of 400 x 400 pixels, and as the control — gray rectangle with a size of 200 x 200 pixels. The relative coordinates and size of the control are also displayed for each combination. Dashes indicate that in those cases setting the size is not obligatory (if automatic resizing mode is enabled). 

Fig. 3. Table listing different options in combination with the control positioning and automatic resizing. 

Fig. 3. Table listing different options in combination with the control positioning and automatic resizing.


5. Added the ON_CLICK_TAB event identifier for generating the event of switching tabs in the classes CTabs and CIconTabs

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
...
#define ON_CLICK_TAB               (27) // Switching tabs

Event with the ON_CLICK_TAB identifier can now be traced in the handler of a custom class, which provides even more capabilities for managing the appearance of the graphical interface. 


6. Forced update of the coordinates has been added to the Show() methods of all controls. As an example, the listing below shows this method of the CSimpleButton class (line highlighted in yellow):

//+------------------------------------------------------------------+
//| Shows the button                                                 |
//+------------------------------------------------------------------+
void CSimpleButton::Show(void)
  {
//--- Leave, if the element is already visible
   if(CElement::IsVisible())
      return;
//--- Make all objects visible
   m_button.Timeframes(OBJ_ALL_PERIODS);
//--- State of visibility
   CElement::IsVisible(true);
//--- Update the position of objects
   Moving(m_wnd.X(),m_wnd.Y());
  }

In certain cases, active use of the MQL application graphical interface could lead to some of its controls to be positioned incorrectly. Now this issue is resolved.


7. The methods for getting the pointers to buttons have been added to the CWindow class: 

//+------------------------------------------------------------------+
//| Class for creating a form for controls                           |
//+------------------------------------------------------------------+
class CWindow : public CElement
  {
public:
   //--- Returns pointers to form buttons
   CBmpLabel        *GetCloseButtonPointer(void)                             { return(::GetPointer(m_button_close));   }
   CBmpLabel        *GetRollUpButtonPointer(void)                            { return(::GetPointer(m_button_unroll));  }
   CBmpLabel        *GetUnrollButtonPointer(void)                            { return(::GetPointer(m_button_rollup));  }
   CBmpLabel        *GetTooltipButtonPointer(void)                           { return(::GetPointer(m_button_tooltip)); }
  };

Thus, the library users now have the ability to change the properties of these graphical objects at any moment of the MQL application's life cycle after the graphical interface had been created. For example, if the button tooltips previously were predefined default values, now then can be set independently. This may be useful when creating multilingual MQL applications.


8. Fixes in the CTable class. Added a check to the main method of creating a control (see the code below). The visible part does not have to be common anymore. Now, if user makes a mistake when setting the table properties, the values will be fixed automatically. 

//+------------------------------------------------------------------+
//| Create edit box table                                            |
//+------------------------------------------------------------------+
bool CTable::CreateTable(const long chart_id,const int subwin,const int x,const int y)
  {
//--- Leave, if there is no form pointer
   if(!CElement::CheckWindowPointer(::CheckPointer(m_wnd)))
      return(false);
//--- The visible part does not have to be common anymore
   m_visible_rows_total    =(m_visible_rows_total>m_rows_total)? m_rows_total : m_visible_rows_total;
   m_visible_columns_total =(m_visible_columns_total>m_columns_total)? m_columns_total : m_visible_columns_total;
//--- Initializing variables
   m_id       =m_wnd.LastId()+1;
   m_chart_id =chart_id;
   m_subwin   =subwin;
   m_x        =x;
   m_y        =y;
   m_x_size   =(m_x_size<1 || m_auto_xresize_mode)? (m_anchor_right_window_side)? m_wnd.X2()-m_x+m_x_size-(m_wnd.X2()-m_x)+1-m_auto_xresize_right_offset : m_wnd.X2()-m_x-m_auto_xresize_right_offset : m_x_size;
   m_y_size   =m_row_y_size*m_visible_rows_total-(m_visible_rows_total-1)+2;
//--- Margins from the edge
   CElement::XGap((m_anchor_right_window_side)? m_x : m_x-m_wnd.X());
   CElement::YGap((m_anchor_bottom_window_side)? m_y : m_y-m_wnd.Y());
//--- Create the table
   if(!CreateArea())
      return(false);
   if(!CreateCells())
      return(false);
   if(!CreateScrollV())
      return(false);
   if(!CreateScrollH())
      return(false);
//--- Hide the element if the window is a dialog one or is minimized
   if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized())
      Hide();
u//---
   return(true);
  }


 

Application for Testing the Updates

For the test, let us slightly change the MQL application from the previous article to make it possible to demonstrate everything that has been presented in this article. Create the EA in subwindow of the “SubWindow” indicator. Main window size of the graphical interface will automatically adjust to the subwindow size. Subwindow height will be manually changeable. To do this, 'false' should be passed (highlighted in green in the code below) when calling the CWindow::RollUpSubwindowMode() method. 

The code listing below also demonstrates how to gain access to managing the properties of the buttons in the main window of the graphical interface in the application. In this case, the example shows setting the tooltips.

//+------------------------------------------------------------------+
//| Creates a form for controls                                      |
//+------------------------------------------------------------------+
bool CProgram::CreateWindow(const string caption_text)
  {
//--- Add the window pointer to the window array
   CWndContainer::AddWindow(m_window);
//--- Coordinates
   int x=1;
   int y=1;
//--- Properties
   m_window.Movable(false);
   m_window.UseRollButton();
   m_window.AutoXResizeMode(true);
   m_window.AutoYResizeMode(true);
   m_window.RollUpSubwindowMode(false,false);
//--- Creating the form
   if(!m_window.CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
//--- Set the tooltips
   m_window.GetCloseButtonPointer().Tooltip("Close program");
   m_window.GetUnrollButtonPointer().Tooltip("Unroll");
   m_window.GetRollUpButtonPointer().Tooltip("Roll up");
   return(true);
  }

On the first tab, all controls will be anchored to the right side of the form (see the screenshot below). If the form width is changed, they will remain at the same distance from its right edge. 

 Fig. 4. Controls of the first tab are anchored to the right side of the form.

Fig. 4. Controls of the first tab are anchored to the right side of the form.


The listing below shows an example of code for creating the "Simple button" (CSimpleButton) control. In order to anchor the control to the right side, it is sufficient to call the AnchorRightWindowSide() method, and pass it the value true

//+------------------------------------------------------------------+
//| Creates simple button 1                                          |
//+------------------------------------------------------------------+
bool CProgram::CreateSimpleButton1(const int x_gap,const int y_gap,string button_text)
  {
//--- Store the window pointer
   m_simple_button1.WindowPointer(m_window);
//--- Attach to the first tab
   m_tabs.AddToElementsArray(0,m_simple_button1);
//--- Coordinates
   int x=m_window.X()+x_gap;
   int y=m_window.Y()+y_gap;
//--- Set properties before creation
   m_simple_button1.ButtonXSize(140);
   m_simple_button1.BackColor(C'255,140,140');
   m_simple_button1.BackColorHover(C'255,180,180');
   m_simple_button1.BackColorPressed(C'255,120,120');
   m_simple_button1.AnchorRightWindowSide(true);
//--- Creating a button
   if(!m_simple_button1.CreateSimpleButton(m_chart_id,m_subwin,button_text,x,y))
      return(false);
//--- Add the element pointer to the base
   CWndContainer::AddToElementsArray(0,m_simple_button1);
   return(true);
  }

The second tab will be assigned only a rendered table (CCanvasTable), which will adjust to the form sizes when the subwindow width and height changes.

 Fig. 5. Rendered table that adjusts to the form sizes.

Fig. 5. Rendered table that adjusts to the form sizes.


For everything to work as intended, it is necessary to use the AutoXResizeMode() and AutoYResizeMode() methods and enable the modes for automatic horizontal and vertical resizing. With the AutoXResizeRightOffset() and AutoYResizeBottomOffset() methods, it is possible to adjust the offsets from the right and bottom borders of the control to the right and bottom edges of the form. In this case, the offset from the right edge is set to 1 pixel, and 25 pixels from the bottom (see the code below). 

//+------------------------------------------------------------------+
//| Create a rendered table                                          |
//+------------------------------------------------------------------+
bool CProgram::CreateCanvasTable(const int x_gap,const int y_gap)
  {
#define COLUMNS3_TOTAL 15
#define ROWS3_TOTAL    30
//--- Store pointer to the form
   m_canvas_table.WindowPointer(m_window);
//--- Attach to the second tab
   m_tabs.AddToElementsArray(1,m_canvas_table);
//--- Coordinates
   int x=m_window.X()+x_gap;
   int y=m_window.Y()+y_gap;
//--- Array of column widths
   int width[COLUMNS3_TOTAL];
   ::ArrayInitialize(width,70);
   width[0]=100;
   width[1]=90;
//--- Array of text alignment in columns
   ENUM_ALIGN_MODE align[COLUMNS3_TOTAL];
   ::ArrayInitialize(align,ALIGN_CENTER);
   align[0]=ALIGN_RIGHT;
   align[1]=ALIGN_RIGHT;
   align[2]=ALIGN_LEFT;
//--- Set properties before creation
   m_canvas_table.XSize(400);
   m_canvas_table.YSize(200);
   m_canvas_table.TableSize(COLUMNS3_TOTAL,ROWS3_TOTAL);
   m_canvas_table.TextAlign(align);
   m_canvas_table.ColumnsWidth(width);
   m_canvas_table.GridColor(clrLightGray);
   m_canvas_table.AutoXResizeMode(true);
   m_canvas_table.AutoYResizeMode(true);
   m_canvas_table.AutoXResizeRightOffset(1);
   m_canvas_table.AutoYResizeBottomOffset(25);
//--- Populate the table with data
   for(int c=0; c<COLUMNS3_TOTAL; c++)
      for(int r=0; r<ROWS3_TOTAL; r++)
         m_canvas_table.SetValue(c,r,string(c)+":"+string(r));
//--- Create control
   if(!m_canvas_table.CreateTable(m_chart_id,m_subwin,x,y))
      return(false);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(0,m_canvas_table);
   return(true);
  }

A line graph (CLineGraph) will be placed on the third tab, also with automatic adjustment to the form sizes:

Fig. 6. The «Line graph» control that adjusts to the form sizes. 

Fig. 6. The «Line graph» control that adjusts to the form sizes.


The fourth and fifth tabs demonstrate the anchoring to right edge of many other library controls:

Fig. 7. Controls on the fourth tab. 

Fig. 7. Controls on the fourth tab.


Fig. 8. Controls on the fifth tab. 

Fig. 8. Controls on the fifth tab.


The test application featured in the article can be downloaded using the below link for further studying. 

 


Conclusion

The library for creating graphical interfaces at the current stage of development looks like in the schematic below.

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

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


In the next version, the library will be expanded with additional controls, which may be necessary in development of MQL applications. The existing controls will be improved and supplemented with new features.

Below you can download the third version of the Easy And Fast library. If you have questions on using material from those files, you can refer to the detailed description of this article series or ask your question in the comments of this article.