Graphical Interfaces I: Form for Controls (Chapter 2)

Anatoli Kazharski | 8 February, 2016

Contents

 

Introduction

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

In the previous chapter we discussed a library structure for creating graphical interfaces. There (1) derived classes for primitive objects, (2) a base class for all controls, (3) principle classes for storing control pointers and managing those controls in the common event handler were created.

In this article we shall create the first and main element of the graphical interface — a form for controls. Multiple controls can be attached to this form anywhere and in any combination. The form will be movable and all controls attached to it will be moved together with it.

 

The Form Class for Controls

As discussed earlier, the description of the CElement class already contained some virtual methods, which will be unique for each control. Let us place their duplicates into the CWindow class and in every further description of other controls we shall do the same. The code for those methods will be considered after we have created methods for building a form (window) for the controls.

//+------------------------------------------------------------------+
//|                                                       Window.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
//+------------------------------------------------------------------+
//| Class for creating a form for controls                           |
//+------------------------------------------------------------------+
class CWindow : public CElement
  {
public:
                     CWindow(void);
                    ~CWindow(void);
   //--- Chart event handler
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Timer
   virtual void      OnEventTimer(void);
   //--- Moving the control
   virtual void      Moving(const int x,const int y);
   //--- Showing, hiding, resetting, deleting
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- Setting, resetting priorities for the left mouse click
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CWindow::CWindow(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CWindow::~CWindow(void)
  {
  }
//+------------------------------------------------------------------+

In the CWindow class and some other classes, many different modes for various actions will be used. Lists of the named constant identifiers for all the modes and data types will be stored in the separate Enums.mqh file. Include it in the Objects.mqh file so all the mode and type enumerations are available in all library files:

//+------------------------------------------------------------------+
//|                                                      Objects.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Enums.mqh"
#include "Defines.mqh"
#include <ChartObjects\ChartObjectsBmpControls.mqh>
#include <ChartObjects\ChartObjectsTxtControls.mqh>

What parts will constitute the window that we are going to create?

  1. Background. All the controls will be located in this area.
  2. Header. This part enables moving the window and contains the interface controls listed below.
  3. Icon. Additional attributes for visual identification.
  4. Caption. Window name.
  5. The "Tooltip" button. Pressing this button enables the mode of showing control tooltips where they are present.
  6. Button for minimizing/maximizing the window.
  7. Button for closing the window.

Fig. 1. Compound parts of the form for controls

Fig. 1. Compound parts of the form for controls

In the graphical interface library, which we are developing, a multi-window mode will be implemented. An application may have only one window. However, when a program has so many options that there is no possibility to put them all in one form, one more form is required. It can be used for some common settings of the application, reference or a window for opening/saving the file and even a palette for selecting a color. So, various ideas may require additional windows. Usually they are called "dialog windows".

The CWindow class must feature an opportunity to define the window type, that is which window is the main application window and which one is the dialog one. Some ideas with new window types may occur during the process of development, but for now, we are going to create an enumeration in the Enums.mqh file containing only two types for (1) the main and (2) dialog windows:

//+------------------------------------------------------------------+
//| Enumeration of window types                                      |
//+------------------------------------------------------------------+
enum ENUM_WINDOW_TYPE
  {
   W_MAIN   =0,
   W_DIALOG =1
  };

The background and the header will be created from the primitive object "Rectangle Label". For that, we have the CRectLabel class. For the caption, we will use the CLabel class, with which the "Text Label" object can be created. For the window icon and buttons listed above, the object "Bitmap Label" is required, and for that we already have the CBmpLabel class.

If the Element.mqh file is already included, then all these classes, defined in the Object.mqh file are available for use. Let us create their instances for each control of the window in the body of the CWindow class and all necessary methods for creating a window:

class CWindow : public CElement
  {
private:
   //--- Objects for creating a form
   CRectLabel        m_bg;
   CRectLabel        m_caption_bg;
   CBmpLabel         m_icon;
   CLabel            m_label;
   CBmpLabel         m_button_tooltip;
   CBmpLabel         m_button_unroll;
   CBmpLabel         m_button_rollup;
   CBmpLabel         m_button_close;
   //---
public:
   //--- Methods for creating a window
   bool              CreateWindow(const long chart_id,const int window,const string caption_text,const int x,const int y);
   //---
private:
   bool              CreateBackground(void);
   bool              CreateCaption(void);
   bool              CreateIcon(void);
   bool              CreateLabel(void);
   bool              CreateButtonClose(void);
   bool              CreateButtonRollUp(void);
   bool              CreateButtonUnroll(void);
   bool              CreateButtonTooltip(void);
  };

Before we start filling these methods, we have to prepare some functionality in the CWndContainer class where arrays will store pointers to all of the interface controls and their constituent parts. We also need to make the following step in the development of the library structure and prepare a program for tests.

Include the Window.mqh file with the CWindow class in the WndContainer.mqh file with the CWndContainer class. We will include all other classes with controls in here too.

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Window.mqh"
// ...
// We will include all other classes with controls in here too
// ...

This way they will be available for use in the CProgram class, that is in the class where the user interface of the MQL application will be created. It will be possible to create and use class members with these data types in the CWndContainer and CWndEvents classes. For instance, if after the inclusion of the Window.mqh file, a dynamic array of pointers of the CWindow type is created (it is not required for work), there will not be any problems and no error messages will be displayed.

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Window.mqh"
//+------------------------------------------------------------------+
//| Class for storing all objects of the interface                   |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- Window array
   CWindow          *m_windows[];
  };

However, if the Window.mqh file was not included, then during creation of a class member with the CWindow type, you would receive an error message as in the screenshot below when you attempt to compile the file:

Fig. 2. Message at compilation that the specified type is missing

Fig. 2. Message at compilation that the specified type is missing

Along with the dynamic window array, we shall need a structure of dynamic arrays of pointers to all controls (CElement) and arrays of all constituent objects (CChartObject). As an instance of this structure, let us create a dynamic array equal in size to the window array (m_window[]). As a result, we will receive an array of pointer arrays to controls for each form (see the code below).

Please note that the m_objects[] array is declared with the CChartObject type. At compilation there will not be any errors as this object type is already present in the structure of the library under development and is included in the Objects.mqh file.

class CWndContainer
  {
protected:
   //--- Structure of control arrays
   struct WindowElements
     {
      //--- Common array of all objects
      CChartObject     *m_objects[];
      //--- Common array of all controls
      CElement         *m_elements[];
     };
   //--- Array of array controls for each window
   WindowElements    m_wnd[];
  };

This is not a complete list of dynamic arrays of the WindowElements structure. As other controls are created, this list will be updated.

To obtain array sizes, appropriate methods are required. The number of objects of all controls and the number of controls for each window can be obtained by having passed the window index (window_index) as a parameter.

class CWndContainer
  {
public:
   //--- Number of windows in the interface
   int               WindowsTotal(void) { return(::ArraySize(m_windows)); }
   //--- Number of objects of all controls
   int               ObjectsElementsTotal(const int window_index);
   //--- Number of controls
   int               ElementsTotal(const int window_index);
  };

We must check for exceeding the array size. Often a repeated phrase can be written as a macro substitution by adding at the beginning a predefined macro substitution containing the name of the (__FUNCTION__) function. Let us write such a macro substitution (PREVENTING_OUT_OF_RANGE) and add this to the Defines.mqh file:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//--- Prevention of exceeding the array size
#define PREVENTING_OUT_OF_RANGE __FUNCTION__," > Prevention of exceeding the array size."

Now, it will be convenient to use in all the functions where we check for exceeding the array size:

//+------------------------------------------------------------------+
//| Returns the number of objects by the specified window index      |
//+------------------------------------------------------------------+
int CWndContainer::ObjectsElementsTotal(const int window_index)
  {
   if(window_index>=::ArraySize(m_wnd))
     {
      ::Print(PREVENTING_OUT_OF_RANGE);
      return(WRONG_VALUE);
     }
//---
   return(::ArraySize(m_wnd[window_index].m_objects));
  }
//+------------------------------------------------------------------+
//| Returns the number of controls by the specified window index     |
//+------------------------------------------------------------------+
int CWndContainer::ElementsTotal(const int window_index)
  {
   if(window_index>=::ArraySize(m_wnd))
     {
      ::Print(PREVENTING_OUT_OF_RANGE);
      return(WRONG_VALUE);
     }
//---
   return(::ArraySize(m_wnd[window_index].m_elements));
  }

Now, imagine that you are a user of this library and go to the CProgram class for the EA created earlier for tests. Let us assume that we need to create a trading panel. For that, we create the CreateTradePanel() method in the CProgram class. Don't forget that to save the space in the article, we only build on top of what has already been done before and discuss only what we add to different classes as we go along.

class CProgram : public CWndEvents
  {
public:
   //--- Creates a trading panel
   bool              CreateTradePanel(void);
  };
//+------------------------------------------------------------------+
//| Creates a trading panel                                          |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Creating a form for controls
// ...
//--- Creating controls
// ...
//---
   ::ChartRedraw();
   return(true);
  }

The body of this method is currently empty but soon it will be filled with the required functionality. From the comments in the method body, it is clear that the window for controls is the first one to create. We need a method where the properties of the created window can be specified. As we have already included the Window.mqh file in the WndContainer.mqh file, now the CWindow class is available in the CProgram class. Therefore, we are going to create an instance of this class and also a method for creating a window for controls CreateWindow():

class CProgram : public CWndEvents
  {
private:
   //--- Form for controls
   CWindow           m_window;
   //---
private:
   //--- Creating a form
   bool              CreateWindow(const string caption_text);
  };

This is the time to answer the following questions:

  1. How will the pointers to controls and their objects find their way into the arrays, which we have previously created in the CWndContainer class?
  2. How will the identifier of each interface control be defined?

Since creating an interface takes a certain (strict) sequence, the process of interface development must be organized in such a way so the developer does not find himself in the situation where he does not know what to do if the sequence is not followed exactly. That is why every action of the library user will be controlled by the engine of the library. Let us arrange it so that it is impossible to create a form for controls while the pointer to it is not in the pointer base in the CWndContainer class. A control will not be created until a pointer to the form it is going to be attached to is not stored in its class.

Let us create the AddWindow() method for adding a window pointer to the base of interface controls in the CWndContainer class. Here will also take place (1) the storing of a pointer in the control array, (2) the storing pointers to control objects in the common array of object pointers, for which we shall need the AddToObjectsArray() method, and also (3) setting the identifier. On top of that, it is required to (4) store the last identifier in the window properties as it will be necessary for defining identifiers for every control inside their classes. This will be possible because as was mentioned in the previous paragraph, every control will have a pointer to the window it is attached to.

Let us start with creating the LastId() methods in the CWindow class for storing and obtaining the identifier of the last created interface control:

class CWindow : public CElement
  {
private:
   //--- Identifier of the last control
   int               m_last_id;
   //---
public:
   //--- Methods for storing and obtaining the id of the last created control
   void              LastId(const int id)                                    { m_last_id=id;                       }
   int               LastId(void)                                      const { return(m_last_id);                  }
  };

Let us create the rest of the methods in the CWndContainer class, which will be used in the AddWindow() method. Every control will have a unique class name, and therefore the AddToObjectsArray() method will be a template one as objects of different controls will be passed into it. In this method, we will iterate over the array of the control objects adding in turn a pointer to each of them in the base array. For that, we will need one more method. Let us name it AddToArray(). As only objects of one type (CChartObject) will be passed to this method, there is no need to make it a template one.

class CWndContainer
  {
protected:
   //--- Adding pointers of control objects to the common array
   template<typename T>
   void              AddToObjectsArray(const int window_index,T &object);
   //--- Adds an object pointer to an array
   void              AddToArray(const int window_index,CChartObject &object);
  };
//+------------------------------------------------------------------+
//| Adds pointers of control objects to the common array             |
//+------------------------------------------------------------------+
template<typename T>
void CWndContainer::AddToObjectsArray(const int window_index,T &object)
  {
   int total=object.ObjectsElementTotal();
   for(int i=0; i<total; i++)
      AddToArray(window_index,object.Object(i));
  }
//+------------------------------------------------------------------+
//| Adds object pointer to an array                                  |
//+------------------------------------------------------------------+
void CWndContainer::AddToArray(const int window_index,CChartObject &object)
  {
   int size=::ArraySize(m_wnd[window_index].m_objects);
   ::ArrayResize(m_wnd[window_index].m_objects,size+1);
   m_wnd[window_index].m_objects[size]=::GetPointer(object);
  }

Now, the AddWindow() method can be created. Here, we will also need a control counter (m_counter_element_id). The value of this variable must be increased every time after adding another control to the base.

class CWndContainer
  {
private:
   //--- Control counter
   int               m_counter_element_id;
   //---
protected:
   //--- Adds window pointer to the base of interface controls
   void              AddWindow(CWindow &object);
  };
//+------------------------------------------------------------------+
//| Adds window pointer to the base of interface controls            |
//+------------------------------------------------------------------+
void CWndContainer::AddWindow(CWindow &object)
  {
   int windows_total=::ArraySize(m_windows);
//--- If there are not any windows, zero the control counter
   if(windows_total<1)
      m_counter_element_id=0;
//--- Add pointer to the window array
   ::ArrayResize(m_wnd,windows_total+1);
   ::ArrayResize(m_windows,windows_total+1);
   m_windows[windows_total]=::GetPointer(object);
//--- Add pointer to the common array of controls
   int elements_total=::ArraySize(m_wnd[windows_total].m_elements);
   ::ArrayResize(m_wnd[windows_total].m_elements,elements_total+1);
   m_wnd[windows_total].m_elements[elements_total]=::GetPointer(object);
//--- Add control objects to the common array of objects
   AddToObjectsArray(windows_total,object);
//--- Set identifier and store the id of the last control
   m_windows[windows_total].Id(m_counter_element_id);
   m_windows[windows_total].LastId(m_counter_element_id);
//--- Increase the counter of control identifiers
   m_counter_element_id++;
  }

From now on, every time a new window of the custom class is created in the MQL application (in the article it is CProgram), you need to add it to the base using the AddWindow() method.

Then, the methods for creating the window declared earlier have to be implemented in the CWindow class. For that, we shall need additional variables and methods related to the type, appearance of the window and also the modes in which the window can be as directed by the user or depending on the type of the MQL application. They are listed below and accompanied by a short description:

  1. Methods for setting and obtaining the window status (minimized/maximized).
  2. Methods for setting and obtaining the window type (main/dialog).
  3. Methods for setting the mode of minimizing the window considering the type of the MQL application being created (EA, indicator). For that, we need methods that would allow us to manage the window size if this is an indicator located not in the main chart window.
  4. Methods for setting the color of each window object by the user.
  5. Method for setting the window label.
  6. Method for defining the window label by default, if the user did not do so.
  7. Identification of the capture area boundaries in the window header.
  8. Constants for button indents from the right edge of the window.
  9. Showing the button for the mode of displaying tooltips.
  10. Variables of priorities for the left mouse click for every window object.

Add everything listed above to the CWindow class. Constants for button indents from the right edge of the form are to be added in the beginning of the file:

//+------------------------------------------------------------------+
//|                                                       Window.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
//--- Button indents from the right edge of the window
#define CLOSE_BUTTON_OFFSET   (20)
#define ROLL_BUTTON_OFFSET    (36)
#define TOOLTIP_BUTTON_OFFSET (53)

Create mentioned variables and methods in the class body:

//+------------------------------------------------------------------+
//| Class for creating a form for controls                           |
//+------------------------------------------------------------------+
class CWindow : public CElement
  {
private:
   //--- Identifier of the last control
   int               m_last_id;
   //--- Status of a minimized window
   bool              m_is_minimized;
   //--- Window type
   ENUM_WINDOW_TYPE  m_window_type;
   //--- Mode of a set height of the sub-window (for indicators)
   bool              m_height_subwindow_mode;
   //--- Mode of minimizing the form in the indicator sub-window
   bool              m_rollup_subwindow_mode;
   //--- Height of the indicator sub-window
   int               m_subwindow_height;
   //--- Properties of the background
   int               m_bg_zorder;
   color             m_bg_color;
   int               m_bg_full_height;
   //--- Properties of the header
   int               m_caption_zorder;
   string            m_caption_text;
   int               m_caption_height;
   color             m_caption_bg_color;
   color             m_caption_bg_color_hover;
   color             m_caption_bg_color_off;
   color             m_caption_color_bg_array[];
   //--- Properties of buttons
   int               m_button_zorder;
   //--- Color of the form frame (background, header)
   color             m_border_color;
   //--- Form icon
   string            m_icon_file;
   //--- Presence of the button for the mode of displaying tooltips
   bool              m_tooltips_button;
   //--- For identifying the capture area boundaries in the window header
   int               m_right_limit;
   //---
public:
   //--- Window type
   ENUM_WINDOW_TYPE  WindowType(void)                                  const { return(m_window_type);              }
   void              WindowType(const ENUM_WINDOW_TYPE flag)                 { m_window_type=flag;                 }
   //--- Default icon
   string            DefaultIcon(void);
   //--- (1) custom icon of the window, (2) use button for reference, (3) limitation of the capture area of the header
   void              IconFile(const string file_path)                        { m_icon_file=file_path;              }
   void              UseTooltipsButton(void)                                 { m_tooltips_button=true;             }
   void              RightLimit(const int value)                             { m_right_limit=value;                }
   //--- Status of a minimized window
   bool              IsMinimized(void)                                 const { return(m_is_minimized);             }
   void              IsMinimized(const bool flag)                            { m_is_minimized=flag;                }
   //--- Properties of the header
   void              CaptionText(const string text);
   string            CaptionText(void)                                 const { return(m_caption_text);             }
   void              CaptionHeight(const int height)                         { m_caption_height=height;            }
   int               CaptionHeight(void)                               const { return(m_caption_height);           }
   void              CaptionBgColor(const color clr)                         { m_caption_bg_color=clr;             }
   color             CaptionBgColor(void)                              const { return(m_caption_bg_color);         }
   void              CaptionBgColorHover(const color clr)                    { m_caption_bg_color_hover=clr;       }
   color             CaptionBgColorHover(void)                         const { return(m_caption_bg_color_hover);   }
   void              CaptionBgColorOff(const color clr)                      { m_caption_bg_color_off=clr;         }
   //--- Window properties
   void              WindowBgColor(const color clr)                          { m_bg_color=clr;                     }
   color             WindowBgColor(void)                                     { return(m_bg_color);                 }
   void              WindowBorderColor(const color clr)                      { m_border_color=clr;                 }
   color             WindowBorderColor(void)                                 { return(m_border_color);             }
   //--- Modes of the indicator sub-window
   void              RollUpSubwindowMode(const bool flag,const bool height_mode);
   //--- Changing the height of the indicator sub-window
   void              ChangeSubwindowHeight(const int height);
  };

Initialization of variables in the class constructor by the default values. In the constructor body we also store the class name of the interface control and set a strict sequence of priorities for left mouse clicking.

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CWindow::CWindow(void) : m_last_id(0),
                         m_subwindow_height(0),
                         m_rollup_subwindow_mode(false),
                         m_height_subwindow_mode(false),
                         m_is_minimized(false),
                         m_tooltips_button(false),
                         m_window_type(W_MAIN),
                         m_icon_file(""),
                         m_right_limit(20),
                         m_caption_height(20),
                         m_caption_bg_color(C'88,157,255'),
                         m_caption_bg_color_off(clrSilver),
                         m_caption_bg_color_hover(C'118,177,255'),
                         m_bg_color(C'15,15,15'),
                         m_border_color(clrLightGray)

  {
//--- Store the name of the control class in the base class
   CElement::ClassName(CLASS_NAME);
//--- Set a strict sequence of priorities
   m_bg_zorder      =0;
   m_caption_zorder =1;
   m_button_zorder  =2;
  }

The implementation of the methods highlighted in yellow in the code above are presented below. If variables and methods from the base class are required, then they are best prefixed with the class name and a double colon (ClassName::). This way the code becomes easier to study and quicker to remember if you have not seen it for a while. The name of the m_subwindow_height variable will be defined automatically when the window is created.

//+------------------------------------------------------------------+
//| Mode of minimizing the indicator sub-window                      |
//+------------------------------------------------------------------+
void CWindow::RollUpSubwindowMode(const bool rollup_mode=false,const bool height_mode=false)
  {
   if(CElement::m_program_type!=PROGRAM_INDICATOR)
      return;
//---
   m_rollup_subwindow_mode =rollup_mode;
   m_height_subwindow_mode =height_mode;
//---
   if(m_height_subwindow_mode)
      ChangeSubwindowHeight(m_subwindow_height);
  }

We shall return to the CWindow::RollUpSubwindowMode() method in one of the following chapters when we are considering practical examples on the chart in the terminal. This way it is clearer what each of the suggested modes represents.

The CWindow::ChangeSubwindowHeight() method allows to change the height of the indicator sub-window, if the program is in such a mode:

//+------------------------------------------------------------------+
//| Changes the height of the indicator sub-window                   |
//+------------------------------------------------------------------+
void CWindow::ChangeSubwindowHeight(const int height)
  {
   if(CElement::m_subwin<1 || CElement::m_program_type!=PROGRAM_INDICATOR)
      return;
//---
   if(height>0)
      ::IndicatorSetInteger(INDICATOR_HEIGHT,height);
  }

 

Methods for Creating a Form

For all compound parts of the window to be visible after creation, they must be set in a strict sequence (layer after layer):

  1. Background.
  2. Header.
  3. Header controls.

In the very beginning of the CWindow::CreateWindow() method where all those constituent parts of the window are going to be created, arrange a check if the pointer to this window is stored in the control base. If this has not been done, then the identifier of the window will have the value of WRONG_VALUE. In that case, the window will not be created and the function will return false, having printed a message to the journal about what action has to be taken. If everything is in order, then variables of the current and base class get initialized by the values of passed parameters. Among those variables are:

Chart identifier and the chart window number are defined in the constructor class CWndEvents. When creating controls in the custom class (CWndEvents::CProgram), these very values are passed to the methods of their creation.

Then, methods for creating all objects of the form follow. If any of the objects are not created, then the function will return false. After instantiating all objects, depending on the program type and set mode, a check must be carried out if the size of the window has to be set (if this is an indicator in a separate chart window). Earlier, I mentioned the m_subwindow_height variable, which gets initialized when the window is created. This is precisely where this initialization happens. In the code below, it is highlighted in yellow.

And, finally, if it is established that this window is a dialog window, this must be hidden. Some explanation is required here. If the program interface contains more than one window, then these windows will be attached to the chart during loading of the program. So, when calling one or another dialog window, the user will not need to keep creating and deleting dialog windows. They will all be present on the chart, they will just be in the hidden mode. When a dialog window is called, it will be visible and when it is closed, it will be hidden but not deleted.

Everything that I said above is reflected in the code below:

//+------------------------------------------------------------------+
//| Creates a form for controls                                      |
//+------------------------------------------------------------------+
bool CWindow::CreateWindow(const long chart_id,const int subwin,const string caption_text,const int x,const int y)
  {
   if(CElement::Id()==WRONG_VALUE)
     {
      ::Print(__FUNCTION__," > Before creating a window, its pointer is to be passed "
            "to the window array using the CWndContainer::AddWindow(CWindow &object) method.");
      return(false);
     }
//--- Initialization of variables
   m_chart_id       =chart_id;
   m_subwin         =subwin;
   m_caption_text   =caption_text;
   m_x              =x;
   m_y              =y;
   m_bg_full_height =m_y_size;
//--- Creating all object of the window
   if(!CreateBackground())
      return(false);
   if(!CreateCaption())
      return(false);
   if(!CreateLabel())
      return(false);
   if(!CreateIcon())
      return(false);
   if(!CreateButtonClose())
      return(false);
   if(!CreateButtonRollUp())
      return(false);
   if(!CreateButtonUnroll())
      return(false);
   if(!CreateButtonTooltip())
      return(false);
//--- If this program is an indicator
   if(CElement::ProgramType()==PROGRAM_INDICATOR)
     {
      //--- If the mode of a set height of the sub-window is set
      if(m_height_subwindow_mode)
        {
         m_subwindow_height=m_bg_full_height+3;
         ChangeSubwindowHeight(m_subwindow_height);
        }
     }
//--- Hide the window, if it is a dialog window
   if(m_window_type==W_DIALOG)
      Hide();
//---
   return(true);
  }

The virtual function Hide() is used for hiding control objects. In the code above, it is highlighted in green. Before, in the CWindow class, we considered only its declaration. In its code, all objects of the window are hidden on all timeframes and the m_is_visible variable of the base class receives the false status.

//+------------------------------------------------------------------+
//| Hides the window                                                 |
//+------------------------------------------------------------------+
void CWindow::Hide(void)
  {
//--- Hide all objects
   for(int i=0; i<ObjectsElementTotal(); i++)
      Object(i).Timeframes(OBJ_NO_PERIODS);
//--- Visible state
   CElement::IsVisible(false);
  }

If you have introduced all the changes above, do not be rushed to compile the file with the CWindow class as you will receive an error message (see the screenshot below). This will happen as all the methods for creating parts of the window currently do not have content. Now, we are going to dedicate some time to their implementation.

Fig. 3. Message about absence of method implementations

Fig. 3. Message about absence of method implementations

Let us start with the background and use the CWindow::CreateBackground() method. In the beginning of each method related to the creation of the object, we shall form the name for this object. It will consist of several parts:

Parts will be separated with the underscore sign «_». The name of the program is saved in the base class CElement in the list of constructor initialization. The control identifier is obtained at the moment of adding the window to the control base.

Then, we shall carry out the check of the size of the window background and its adjustment depending on if the window is minimized or maximized. We are going to look ahead. When the timeframe or the symbol of the chart is switched, all objects will be moved to the functions of the program uninitialization. In the function of the program initialization, they will be restored according to the current values of the class fields (variables).

After the adjustment of the background size, the object setting is carried out, followed by the setting of properties. At the very end of the method, the object pointer is saved in the base class array. The code of the CWindow::CreateBackground() function is presented below:

//+------------------------------------------------------------------+
//| Creates the window background                                    |
//+------------------------------------------------------------------+
bool CWindow::CreateBackground(void)
  {
//--- Formation of the window name
   string name=CElement::ProgramName()+"_window_bg_"+(string)CElement::Id();
//--- Size of the window depends of its state (minimized/maximized)
   int y_size=0;
   if(m_is_minimized)
     {
      y_size=m_caption_height;
      CElement::YSize(m_caption_height);
     }
   else
     {
      y_size=m_bg_full_height;
      CElement::YSize(m_bg_full_height);
     }
//--- Set the window background
   if(!m_bg.Create(m_chart_id,name,m_subwin,m_x,m_y,m_x_size,y_size))
      return(false);
//--- Set properties
   m_bg.BackColor(m_bg_color);
   m_bg.Color(m_border_color);
   m_bg.BorderType(BORDER_FLAT);
   m_bg.Corner(m_corner);
   m_bg.Selectable(false);
   m_bg.Z_Order(m_bg_zorder);
   m_bg.Tooltip("\n");
//--- Store the object pointer
   CElement::AddToArray(m_bg);
   return(true);
  }

In the window header creation method, besides basic actions similar to how it was implemented in the window background creation method, it is required to save coordinates and sizes in the primitive object CRectLabel class, which is located in the Objects.mqh file. The values of those fields will be updated when the window is displaced (and therefore its coordinates change) and when its sizes change. If these values are static, it will be impossible to track the cursor over the header. This rule will be applicable to all other objects in all other controls.

Another difference lies in the ability of the header to react to the mouse cursor when it is in the window area or when the cursor leaves the window area. For that, the InitColorArray() method was created in the CElement class.

Code of the CElement::CreateCaption() method:

//+------------------------------------------------------------------+
//| Creates the window header                                        |
//+------------------------------------------------------------------+
bool CWindow::CreateCaption(void)
  {
   string name=CElement::ProgramName()+"_window_caption_"+(string)CElement::Id();
//--- Set the window header
   if(!m_caption_bg.Create(m_chart_id,name,m_subwin,m_x,m_y,m_x_size,m_caption_height))
      return(false);
//--- Set properties
   m_caption_bg.BackColor(m_caption_bg_color);
   m_caption_bg.Color(m_border_color);
   m_caption_bg.BorderType(BORDER_FLAT);
   m_caption_bg.Corner(m_corner);
   m_caption_bg.Selectable(false);
   m_caption_bg.Z_Order(m_caption_zorder);
   m_caption_bg.Tooltip("\n");
//--- Store coordinates
   m_caption_bg.X(m_x);
   m_caption_bg.Y(m_y);
//--- Store sizes (in object)
   m_caption_bg.XSize(m_caption_bg.X_Size());
   m_caption_bg.YSize(m_caption_bg.Y_Size());
//--- Initializing the array gradient
   CElement::InitColorArray(m_caption_bg_color,m_caption_bg_color_hover,m_caption_color_bg_array);
//--- Store the object pointer
   CElement::AddToArray(m_caption_bg);
   return(true);
  }

The program label in the main window of the interface will be represented by default by icons prepared previously. Which one of them will be reflected will depend on the type of the program being developed (ENUM_PROGRAM_TYPE). All label icons can be downloaded at the end of the article. To use in programs, those label icons that are not clickable controls should be placed in the directory <data folder>\MQLX\Images\EasyAndFastGUI\Icons\bmp16. The name bmp16 means that the folder contains icons of size 16x16 pixels. If your collection of library icons consists of ones that are 24x24 in size, then your folder will be called bmp24 etc.

Standard icons that are used by default for creating controls, must be placed in the directory <data folder>\MQLX\Images\EasyAndFastGUI\Controls.

For automatic identifying of the main program window label, the DefaultIcon() method was declared in the CWindow class. Below is the code of this method:

//+------------------------------------------------------------------+
//| Identifying the default label                                    |
//+------------------------------------------------------------------+
string CWindow::DefaultIcon(void)
  {
   string path="Images\\EasyAndFastGUI\\Icons\\bmp16\\advisor.bmp";
//---
   switch(CElement::ProgramType())
     {
      case PROGRAM_SCRIPT:
        {
         path="Images\\EasyAndFastGUI\\Icons\\bmp16\\script.bmp";
         break;
        }
      case PROGRAM_EXPERT:
        {
         path="Images\\EasyAndFastGUI\\Icons\\bmp16\\advisor.bmp";
         break;
        }
      case PROGRAM_INDICATOR:
        {
         path="Images\\EasyAndFastGUI\\Icons\\bmp16\\indicator.bmp";
         break;
        }
     }
//---
   return(path);
  }

It must be noted that only interface controls concerned with the data visualization can be used in scripts. The explanation here is that scripts do not provide an opportunity to use functions for tracking events.

If the icon for the window must be redefined, the CWindow::IconFile() method can be used. Examples of this and other methods will be considered when we test interfaces based on this library on the chart in the terminal.

Resources with icons for different controls will be included (#resource) next to the method (outside the method body), where they are going to be used (highlighted in yellow). The label will always have one state (one icon), and therefore a path to only one version will be specified in the methods of the primitive objects CChartObjectBmpLabel::BmpFileOn() and CChartObjectBmpLabel::BmpFileOff().

All object coordinates (except the window header) of all controls of each window are calculated in relation to coordinates (top left corner) of this window in local variables of this method. Consequently, all object indentations relative to the window coordinates are stored in the corresponding fields of the primitive object class in the Objects.mqh file. They are required to not calculate coordinates again when a window gets relocated. That means only one operation instead of two is required for updating the coordinates of each object of all controls, like at the moment of object setting. In code below, absence of indents from the edge point of the window is highlighted in green.

//+------------------------------------------------------------------+
//| Creates the program label                                        |
//+------------------------------------------------------------------+
//--- Icons (by default) symbolizing the program type
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\advisor.bmp"
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\indicator.bmp"
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\script.bmp"
//---
bool CWindow::CreateIcon(void)
  {
   string name=CElement::ProgramName()+"_window_icon_"+(string)CElement::Id();
//--- Object coordinates
   int x=m_x+5;
   int y=m_y+2;
//--- Set the window icon
   if(!m_icon.Create(m_chart_id,name,m_subwin,x,y))
      return(false);
//--- Default icon, if not specified by the user
   if(m_icon_file=="")
      m_icon_file=DefaultIcon();
//--- Set properties
   m_icon.BmpFileOn("::"+m_icon_file);
   m_icon.BmpFileOff("::"+m_icon_file);
   m_icon.Corner(m_corner);
   m_icon.Selectable(false);
   m_icon.Z_Order(m_button_zorder);
   m_icon.Tooltip("\n");
//--- Store coordinates
   m_icon.X(x);
   m_icon.Y(y);   
//--- Indents from the edge point
   m_icon.XGap(x-m_x);
   m_icon.YGap(y-m_y);
//--- Store sizes (in object)
   m_icon.XSize(m_icon.X_Size());
   m_icon.YSize(m_icon.Y_Size());
//--- Add objects to the group array
   CElement::AddToArray(m_icon);
   return(true);
  }

As code of the method for creating a text label of the header CWindow::CreateLabel() does not have any peculiarities different to what was described in the method for creating the program label, we will not discuss it here to save the space in the article. You can find its code in the Window.mqh file at the end of this article.

Now, we are going to discuss the buttons that will be located in the window header. All of these buttons will have rules that will make them different from the methods described above:

As for the functionality of minimizing and maximizing the window, it will require two buttons. They will be located one above the other and visibility of each of them will depend on what state the window is in. When the window is maximized, then the button for minimizing the window will be visible and vice versa.

In everything else, these buttons are not different from other methods for creating parts of a control form. More detailed code for the CreateButtonClose(), CreateButtonRollUp(), CreateButtonUnroll() and CreateButtonTooltip() methods can be found in the attached file Window.mqh at the end of the article.

Icons for all buttons that will be used in the examples below, can also be downloaded at the end of the article. You can use your own icons. In that case, keep in mind that the same names of bmp-files like in the attached code must be stored or the path to bmp-files must be changed in the code if the names of your files are different from the ones suggested in this article.

 

Attaching a Form to a Chart

Finally, we are at the stage when we can test a part of what has been done and see a result. Let us take a look at what a control form looks like on the chart. The functionality for creating this form was created above.

We are going to continue working in the file for tests, which was created earlier. There we stopped at creating a common method for creating the CProgram::CreateTradePanel() interface and the method for creating the form for controls CProgram::CreateWindow(). Below is the code for creating a form. Please note how the background color and the form header can be redefined.

//+------------------------------------------------------------------+
//| Creates a form for controls                                      |
//+------------------------------------------------------------------+
bool CProgram::CreateWindow(const string caption_text)
  {
//--- Add a window pointer to the window array
   CWndContainer::AddWindow(m_window);
//--- Properties
   m_window.XSize(200);
   m_window.YSize(200);
   m_window.WindowBgColor(clrWhiteSmoke);
   m_window.WindowBorderColor(clrLightSteelBlue);
   m_window.CaptionBgColor(clrLightSteelBlue);
   m_window.CaptionBgColorHover(C'200,210,225');
//--- Creating a form
   if(!m_window.CreateWindow(m_chart_id,m_subwin,caption_text,1,1))
      return(false);
//---
   return(true);
  }

In the common method CProgram::CreateTradePanel() call the CProgram::CreateWindow() method, having passed a text for the window header as the only parameter:

//+------------------------------------------------------------------+
//| Creates a trading panel                                          |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Creating a form for controls
   if(!CreateWindow("EXPERT PANEL"))
      return(false);
//--- Creating controls
// ...
//---
   ::ChartRedraw();
   return(true);
  }

Now, we only have to add a call of the CProgram::CreateTradePanel() method in the main file of the program in the OnInit() function. In case there is an error when creating the program interface, print a correspondent message in the journal and finish work:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
   program.OnInitEvent();
//--- Set a trading panel
   if(!program.CreateTradePanel())
     {
      ::Print(__FUNCTION__," > Failed to create graphical interface!");
      return(INIT_FAILED);
     }
//--- Initialization successful
   return(INIT_SUCCEEDED);
  }

Compile the code and load the program on to the chart. If everything is correct, you will see a form on the chart like in the screenshot below:

Fig. 4. First test of attaching the form to the chart

Fig. 4. First test of attaching the form to the chart

If you forget to add a window pointer to the pointer base when developing an MQL application, you will still be able to compile the code. However, when you try to load the program onto the chart, you will see that the program will be instantly deleted and the following message will be printed in the journal of the "Expert Advisors" tab (the "Tools" panel):

2015.09.23 19:26:10.398 TestLibrary (EURUSD,D1) OnInit > Failed to create graphical interface!
2015.09.23 19:26:10.398 TestLibrary (EURUSD,D1) CWindow::CreateWindow > Before creating a window, its pointer is to be saved in the base: CWndContainer::AddWindow(CWindow &object).

More detailed examples of using the methods of the CWindow class in an indicator and script, will be shown in the following chapters (articles) of the series.

 

Conclusion

The current structure of the library being developed is illustrated in the diagram below:

Fig. 5. Adding the main interface control and a form for controls to the project.

Fig. 5. Adding the main interface control and a form for controls to the project

All classes relative to the interface controls and are derived from their CElement base class are located in the large rectangle with a thick blue arrow. All interface elements will be included in the file with the CWndContainer class.

You can download to your computer all the material of the first part of the series so you can test it. If you have questions on how to use the material, presented in these files, you can refer to the detailed description of the library development in one of the articles from the list below or ask your question in the comments below.

List of articles (chapters) of the first part: