Graphical Interfaces III: Groups of Simple and Multi-Functional Buttons (Chapter 2)

Anatoli Kazharski | 5 April, 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 full list of the links to the articles is at the end of each chapter of the series. 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.

The first article of the third part of the series was dedicated to simple and multi-functional buttons. In the second article, we will discuss groups of interconnected buttons. They are used for creating elements in applications allowing a user to select one of the options from a set (group).

 


Developing the Class for Creating Groups of Simple Buttons

A group of simple buttons is essentially an array of graphical objects of the OBJ_BUTTON type. The differential characteristic of such controls is that only one button of the group can be pressed at a time. At this stage, a class of this control can be created in two ways:

  1. by creating a group from already implemented control of the CSimpleButton type;
  2. by creating a group from primitive objects of the CButton type.

The second option is simpler as it does not require the creation of an additional method for each control of the CSimpleButton type to get to the base of pointers. We are going to use this one. 

Create the ButtonsGroup.mqh file with the CButtonsGroup class in the Controls folder containing all controls and include it in the WndContainer.mqh file. The class must have virtual methods and the form pointer as it has been shown for all other previously developed controls. We are not going to discuss them here and go straight to the description of properties and methods for their construction. 

Some properties will be common for each button inside one group but there will be unique ones too. Below are two groups of properties related to the appearance of buttons.

Common properties of buttons:

Unique properties of buttons:

Margins for each button will allow to arrange buttons in any sequence.

Fig. 1.  Examples of arranging buttons in a group.

Fig. 1.  Examples of arranging buttons in a group.


Declare dynamic arrays for the objects of the CButton type and unique properties of buttons as shown in the code below. 

//+------------------------------------------------------------------+
//| Class for creating a group of simple buttons                     |
//+------------------------------------------------------------------+
class CButtonsGroup : public CElement
  {
private:
   //--- Objects for creating a button
   CButton           m_buttons[];
   //--- Button gradients
   struct ButtonsGradients
     {
      color             m_buttons_color_array[];
     };
   ButtonsGradients  m_buttons_total[];
   //--- Button properties:
   //    Arrays for unique properties of buttons
   bool              m_buttons_state[];
   int               m_buttons_x_gap[];
   int               m_buttons_y_gap[];
   string            m_buttons_text[];
   int               m_buttons_width[];
   color             m_buttons_color[];
   color             m_buttons_color_hover[];
   color             m_buttons_color_pressed[];
   //--- Height of buttons
   int               m_button_y_size;
   //--- Color of blocked buttons
   color             m_back_color_off;
   //--- Color frame in the active and blocked modes
   color             m_border_color;
   color             m_border_color_off;
   //--- Text color
   color             m_text_color;
   color             m_text_color_off;
   color             m_text_color_pressed;
   //--- Priority of left mouse click
   int               m_buttons_zorder;
   //---
public:
   //--- Number of buttons
   int               ButtonsTotal(void)                       const { return(::ArraySize(m_buttons));  }
   //--- (1) Height of buttons
   void              ButtonYSize(const int y_size)                  { m_button_y_size=y_size;          }
   //--- (1) Background colors of a blocked button and a frame ((2) available/(3) blocked)
   void              BackColorOff(const color clr)                  { m_back_color_off=clr;            }
   void              BorderColor(const color clr)                   { m_border_color=clr;              }
   void              BorderColorOff(const color clr)                { m_border_color_off=clr;          }
   //--- Text color
   void              TextColor(const color clr)                     { m_text_color=clr;                }
   void              TextColorOff(const color clr)                  { m_text_color_off=clr;            }
   void              TextColorPressed(const color clr)              { m_text_color_pressed=clr;        }
  };

The size of dynamic arrays will be defined at the time of forming a group of buttons before its creation (attaching to the chart). Every time the CButtonsGroup::AddButton() method is called, the arrays will be increased by one element which will be filled with passed parameters.

class CButtonsGroup : public CElement
  {
public:
   //--- Adds a button with specified properties before creation
   void              AddButton(const int x_gap,const int y_gap,const string text,const int width,
                               const color button_color,const color button_color_hover,const color button_color_pressed);
  };
//+------------------------------------------------------------------+
//| Adds a button                                                    |
//+------------------------------------------------------------------+
void CButtonsGroup::AddButton(const int x_gap,const int y_gap,const string text,const int width,
                              const color button_color,const color button_color_hover,const color pressed_button_color)
  {
//--- Increase the array size by one element
   int array_size=::ArraySize(m_buttons);
   ::ArrayResize(m_buttons,array_size+1);
   ::ArrayResize(m_buttons_total,array_size+1);
   ::ArrayResize(m_buttons_state,array_size+1);
   ::ArrayResize(m_buttons_x_gap,array_size+1);
   ::ArrayResize(m_buttons_y_gap,array_size+1);
   ::ArrayResize(m_buttons_text,array_size+1);
   ::ArrayResize(m_buttons_width,array_size+1);
   ::ArrayResize(m_buttons_color,array_size+1);
   ::ArrayResize(m_buttons_color_hover,array_size+1);
   ::ArrayResize(m_buttons_color_pressed,array_size+1);
//--- Store the values of passed parameters
   m_buttons_x_gap[array_size]         =x_gap;
   m_buttons_y_gap[array_size]         =y_gap;
   m_buttons_text[array_size]          =text;
   m_buttons_width[array_size]         =width;
   m_buttons_color[array_size]         =button_color;
   m_buttons_color_hover[array_size]   =button_color_hover;
   m_buttons_color_pressed[array_size] =pressed_button_color;
   m_buttons_state[array_size]         =false;
  }

When creating a group of buttons, the process of creating the graphical interface will be stopped and a relevant message will be printed in the journal if before that no buttons were added using the CButtonsGroup::AddButton() method. Buttons will be created in a loop as shown in the shortened version of the CButtonsGroup::CreateButtons() method in the code below. 

class CButtonsGroup : public CElement
  {
public:
   //--- Methods for creating a button
   bool              CreateButtonsGroup(const long chart_id,const int subwin,const int x,const int y);
   //---
private:
   bool              CreateButtons(void);
  };
//+------------------------------------------------------------------+
//| Creates buttons                                                  |
//+------------------------------------------------------------------+
bool CButtonsGroup::CreateButtons(void)
  {
//--- Coordinates
   int l_x =m_x;
   int l_y =m_y;
//--- Get the number of buttons
   int buttons_total=ButtonsTotal();
//--- If there is no button in a group, report
   if(buttons_total<1)
     {
      ::Print(__FUNCTION__," > This method is to be called "
              "if a group contains at least one button! Use the CButtonsGroup::AddButton() method");
      return(false);
     }
//--- Create the specified number of buttons
   for(int i=0; i<buttons_total; i++)
     {
      //--- Forming the object name
      //--- Calculating coordinates
      //--- Set up a button
      //--- Setting up properties
      //--- Store the margins from the edge of the panel, coordinates and size
      //--- Initializing the gradient array
      //--- Store the object pointer
     }
//---
   return(true);
  }

Let us create two modes for this type of button group. To set this up, add a designated field and method to the class as shown in the code below. The default value will be false, which means that the mode allowing to have all buttons unpressed in a group is enabled. The default value true means that one button in a group will always be pressed.

class CButtonsGroup : public CElement
  {
public:
   //--- The radio button mode
   bool              m_radio_buttons_mode;
   //---
public:
   //--- Installing the radio button mode
   void              RadioButtonsMode(const bool flag)              { m_radio_buttons_mode=flag;       }
  };

As in any other control, this class must contain a method for blocking the control. For this type of button group, it is important to ensure that when unblocking a previously pressed button it restores the appearance of its state

class CButtonsGroup : public CElement
  {
private:
   //--- Available/blocked
   bool              m_buttons_group_state;
   //---
public:
   //--- General state of the button group (available/blocked)
   bool              ButtonsGroupState(void)                  const { return(m_buttons_group_state);   }
   void              ButtonsGroupState(const bool state);
  };
//+------------------------------------------------------------------+
//| Changing the state of buttons                                    |
//+------------------------------------------------------------------+
void CButtonsGroup::ButtonsGroupState(const bool state)
  {
   m_buttons_group_state=state;
//---
   int buttons_total=ButtonsTotal();
   for(int i=0; i<buttons_total; i++)
     {
      m_buttons[i].State(false);
      m_buttons[i].Color((state)? m_text_color : m_text_color_off);
      m_buttons[i].BackColor((state)? m_buttons_color[i]: m_back_color_off);
      m_buttons[i].BorderColor((state)? m_border_color : m_border_color_off);
     }
//--- Press the button if it was pressed before blocking
   if(m_buttons_group_state)
     {
      if(m_selected_button_index!=WRONG_VALUE)
        {
         m_buttons_state[m_selected_button_index]=true;
         m_buttons[m_selected_button_index].Color(m_text_color_pressed);
         m_buttons[m_selected_button_index].BackColor(m_buttons_color_pressed[m_selected_button_index]);
        }
     }
  }

We need a method for toggling the button state when pressing. We will also require fields and methods for storing and getting a text and index of the highlighted button.

At the beginning of the method for toggling the button state in the code below there is a check for the number of buttons in a group. If it turns out that there are no buttons at all, a relevant message will be printed in the journal. The program will not leave the method. The program will carry on and at some point will encounter an error of exceeding the m_buttons_state[] array range. This means that the developer of the application must add at least one button to the group to avoid such an error. After the check, if there is at least one button, the program will adjust the passed index in case the array range has been exceeded. Then, the state of the button is changed by the specified index. After that, all buttons except the pressed one become unpressed (the relevant color is set up) in a loop considering the group mode. In other words, if the radio button mode is enabled when at least one button must be pressed at a time, then during every iteration a check is carried out for a condition of the equality of the passed index to the current index of the loop. If the mode is enabled when all buttons can be unpressed, then in addition to the check for the index, the current state of the button is checked. If the condition is met, then the pressed button flag is set irrespective of the mode. At the end of the method, in the class where the text and index of the highlighted button will be saved, its fields will get corresponding values by this flag. If there is not a pressed button, empty values are set ("" and WRONG_VALUE). 

class CButtonsGroup : public CElement
  {
private:
   //--- (1) Text and (2) index of the highlighted button
   string            m_selected_button_text;
   int               m_selected_button_index;
   //---
public:
   //--- Returns (1) the text and (2) index of the highlighted button
   string            SelectedButtonText(void)                 const { return(m_selected_button_text);  }
   int               SelectedButtonIndex(void)                const { return(m_selected_button_index); }
   //--- Toggles the button state by the specified index
   void              SelectionButton(const int index);
  };
//+------------------------------------------------------------------+
//| Toggles the button state by the specified index                  |
//+------------------------------------------------------------------+
void CButtonsGroup::SelectionButton(const int index)
  {
//--- For checking for a pressed button in a group
   bool check_pressed_button=false;
//--- Get the number of buttons
   int buttons_total=ButtonsTotal();
//--- If there is no button in a group, report
   if(buttons_total<1)
     {
      ::Print(__FUNCTION__," > This method is to be called "
              "if a group contains at least one button! Use the CButtonsGroup::AddButton() method");
     }
//--- Adjust the index value if the array range is exceeded
   int correct_index=(index>=buttons_total)? buttons_total-1 : (index<0)? 0 : index;
//--- Change the button state for the opposite
   m_buttons_state[correct_index]=(m_buttons_state[correct_index])? false : true;
//--- Iterate over a group of buttons
   for(int i=0; i<buttons_total; i++)
     {
      //--- A relevant check is carried out depending on the mode
      bool condition=(m_radio_buttons_mode)? (i==correct_index) : (i==correct_index && m_buttons_state[i]);
      //--- If the condition is met, make the button pressed
      if(condition)
        {
         if(m_radio_buttons_mode)
            m_buttons_state[i]=true;
         //--- There is a pressed button
         check_pressed_button=true;
         //--- Set a color
         m_buttons[i].Color(m_text_color_pressed);
         m_buttons[i].BackColor(m_buttons_color_pressed[i]);
         CElement::InitColorArray(m_buttons_color_pressed[i],m_buttons_color_pressed[i],m_buttons_total[i].m_buttons_color_array);
        }
      //--- If the condition is not met, make the button unpressed
      else
        {
         //--- Set the disabled state and colors
         m_buttons_state[i]=false;
         m_buttons[i].Color(m_text_color);
         m_buttons[i].BackColor(m_buttons_color[i]);
         CElement::InitColorArray(m_buttons_color[i],m_buttons_color_hover[i],m_buttons_total[i].m_buttons_color_array);
        }
      //--- Zero the normal state of the button
      m_buttons[i].State(false);
     }
//--- If there is a pressed button, store its text and index
   m_selected_button_text  =(check_pressed_button) ? m_buttons[correct_index].Description() : "";
   m_selected_button_index =(check_pressed_button) ? correct_index : WRONG_VALUE;
  }

To handle the pressing of a group button, create the CButtonsGroup::OnClickButton() method. Similar to many other homonym methods, considered before, the following checks are carried out:

If all checks have been passed and the program has not left the method, toggling the button state in the group will be carried out in the CButtonsGroup::SelectionButton() method considered before. At the end of this method, a message containing the data about the pressed button is sent to the stream of events. This message can be received in the handler of the custom class.

class CButtonsGroup : public CElement
  {
private:
   //--- Handling the pressing of the button
   bool              OnClickButton(const string clicked_object);
   //--- Getting the identifier from the button name
   int               IdFromObjectName(const string object_name);
  };
//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void CButtonsGroup::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handling the left mouse button click event on the object
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      if(OnClickButton(sparam))
         return;
     }
  }
//+------------------------------------------------------------------+
//| Pressing a button in a group                                     |
//+------------------------------------------------------------------+
bool CButtonsGroup::OnClickButton(const string pressed_object)
  {
//--- Leave, if the pressing was not on the menu item
   if(::StringFind(pressed_object,CElement::ProgramName()+"_buttons_",0)<0)
      return(false);
//--- Get the identifier from the object name
   int id=IdFromObjectName(pressed_object);
//--- Leave, if identifiers do not match
   if(id!=CElement::Id())
      return(false);
//--- For checking the index
   int check_index=WRONG_VALUE;
//--- Check, if the pressing was on one of the buttons of this group
   int buttons_total=ButtonsTotal();
//--- Leave, if the buttons are blocked
   if(!m_buttons_group_state)
     {
      for(int i=0; i<buttons_total; i++)
         m_buttons[i].State(false);
      //---
      return(false);
     }
//--- If the pressing took place, store the index
   for(int i=0; i<buttons_total; i++)
     {
      if(m_buttons[i].Name()==pressed_object)
        {
         check_index=i;
         break;
        }
     }
//--- Leave, if the button of this group was not pressed
   if(check_index==WRONG_VALUE)
      return(false);
//--- Toggle the button state
   SelectionButton(check_index);
//--- Send a signal about it
   ::EventChartCustom(m_chart_id,ON_CLICK_BUTTON,CElement::Id(),m_selected_button_index,m_selected_button_text);
   return(true);
  }

Now, we need to set up the reaction of the group buttons to the mouse cursor hovering over them and the left mouse click. For that, let us write the CButtonsGroup::CheckPressedOverButton() method, which will be called in the control handler during handling the CHARTEVENT_MOUSE_MOVE event. Before the method is called, the following checks will be carried out: (1) if the control is visible, (2) if it is available, (3) if the form is available and (4) if the left mouse button is pressed.

class CButtonsGroup : public CElement
  {
private:
   //--- Checking the pressed left mouse button over the group buttons
   void              CheckPressedOverButton(void);
  };
//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void CButtonsGroup::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handling the cursor movement event
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Leave, if the control is hidden
      if(!CElement::IsVisible())
         return;
      //--- Leave, if the buttons are blocked
      if(!m_buttons_group_state)
         return;
      //--- Define the focus
      int x=(int)lparam;
      int y=(int)dparam;
      int buttons_total=ButtonsTotal();
      for(int i=0; i<buttons_total; i++)
        {
         m_buttons[i].MouseFocus(x>m_buttons[i].X() && x<m_buttons[i].X2() && 
                                 y>m_buttons[i].Y() && y<m_buttons[i].Y2());
        }
      //--- Leave, if the form is blocked
      if(m_wnd.IsLocked())
         return;
      //--- Leave, if the mouse button is not pressed
      if(sparam!="1")
         return;
      //--- Checking the pressed left mouse button over the group buttons
      CheckPressedOverButton();
      return;
     }
  }
//+------------------------------------------------------------------+
//| Checking the pressed left mouse button over the group buttons    |
//+------------------------------------------------------------------+
void CButtonsGroup::CheckPressedOverButton(void)
  {
   int buttons_total=ButtonsTotal();
//--- Set the color depending on the location of the left mouse button press
   for(int i=0; i<buttons_total; i++)
     {
      //--- If there is a focus, then the color of the pressed button
      if(m_buttons[i].MouseFocus())
         m_buttons[i].BackColor(m_buttons_color_pressed[i]);
      //--- If there is no focus, then...
      else
        {
         //--- ...if a group button is not pressed, assign the background color
         if(!m_buttons_state[i])
            m_buttons[i].BackColor(m_buttons_color[i]);
        }
     }
  }

Everything is ready for testing a group of simple buttons in the test application. Make a copy of the EA that we have previously tested. Remove all controls except the main menu with its context menus. We will use this program for testing various button groups in this article.

Create an instance of the CButtonsGroup class of the button group in the custom class. Declare the CProgram::CreateButtonsGroup1() method for its creation and margins from the edge of the form. 

class CProgram : public CWndEvents
  {
private:
   //--- Group of simple buttons
   CButtonsGroup     m_buttons_group1;
   //---
private:
   //--- Group of simple buttons
#define BUTTONS_GROUP1_GAP_X (7)
#define BUTTONS_GROUP1_GAP_Y (50)
   bool              CreateButtonsGroup1(void);
  };

Create a group of four buttons. Arrange them horizontally. For this example, we will make two buttons red and two buttons blue. The implementation of the CProgram::CreateButtonsGroup1() method is shown in the code below. If you need one button to be highlighted straight after the button group was created, use the CButtonsGroup::SelectionButton() public method

//+------------------------------------------------------------------+
//| Creates a group of simple buttons                                |
//+------------------------------------------------------------------+
bool CProgram::CreateButtonsGroup1(void)
  {
//--- Store the window pointer
   m_buttons_group1.WindowPointer(m_window);
//--- Coordinates
   int x =m_window.X()+BUTTONS_GROUP1_GAP_X;
   int y =m_window.Y()+BUTTONS_GROUP1_GAP_Y;
//--- Properties
   int    buttons_x_gap[]         ={0,72,144,216};
   string buttons_text[]          ={"BUTTON 1","BUTTON 2","BUTTON 3","BUTTON 4"};
   int    buttons_width[]         ={70,70,70,70};
   color  buttons_color[]         ={C'195,0,0',C'195,0,0',clrRoyalBlue,clrRoyalBlue};
   color  buttons_color_hover[]   ={C'255,51,51',C'255,51,51',C'85,170,255',C'85,170,255'};
   color  buttons_color_pressed[] ={C'135,0,0',C'135,0,0',C'50,100,135',C'50,100,135'};
//--- Set properties
   m_buttons_group1.TextColor(clrWhite);
   m_buttons_group1.TextColorPressed(clrGold);
//--- Add four buttons to a group
   for(int i=0; i<4; i++)
      m_buttons_group1.AddButton(buttons_x_gap[i],0,buttons_text[i],buttons_width[i],
                                 buttons_color[i],buttons_color_hover[i],buttons_color_pressed[i]);
//--- Create a group of buttons
   if(!m_buttons_group1.CreateButtonsGroup(m_chart_id,m_subwin,x,y))
      return(false);
//--- Highlight the second button in the group
   m_buttons_group1.SelectionButton(1);
//--- Add an object to the common array of object groups
   CWndContainer::AddToElementsArray(0,m_buttons_group1);
   return(true);
  }

After compiling files and loading the program on the chart, you should get the result as shown in the screenshot below.

Fig. 2. Test of the group of simple buttons control.

Fig. 2. Test of the group of simple buttons control.


We have finished with the first group of buttons. The complete version of the CButtonsGroup class can be downloaded at the end of this article. The next control to consider is a group of radio buttons. 


 


Developing the Class for Creating Groups of Radio Buttons

Create the RadioButtons.mqh file with the CRadioButtons class which must contain standard virtual methods and class members for storing and getting the form pointer. You can see examples in the classes of other controls above. Include the RadioButtons.mqh file in the library (WndContainer.mqh).

Each radio item will be composed of thee primitive objects:

  1. background;
  2. icon;
  3. text label.

Fig. 3. Compound parts of radio buttons.

Fig. 3. Compound parts of radio buttons.


Unlike a group of simple buttons, a group of radio buttons will have only one mode. This is because in this control all the buttons cannot be unpressed simultaneously. One button in this group will always be pressed. The background color of the group is usually the same as the background of the form to which the group is attached. Essentially, it is used for identifying the focus and tracking the pressing of a radio button (higher priority). In this version, we can set up the color change only for the text label when the cursor gets in the area of the button background and leaves it. The lists of unique and common properties of a radio button are different to the lists of properties of a simple button as shown below.

Common properties:

Unique properties:


You can see what it looks like in the code of the CRadioButtons class below. The sizing of the unique properties arrays and filling with values are carried out with the CRadioButtons::AddButton() public method before creating the control in the custom class. If no buttons were added, then the creation of the graphical interface will be terminated. This has been shown in the development of the class for creating a group of simple buttons and we are not going to spend more time on it.

//+------------------------------------------------------------------+
//| Class for creating groups of radio buttons                       |
//+------------------------------------------------------------------+
class CRadioButtons : public CElement
  {
private:
   //--- Gradients of text labels
   struct LabelsGradients
     {
      color             m_labels_color_array[];
     };
   LabelsGradients   m_labels_total[];
   //--- Button properties:
   //    (1) Color and (2) priority of the background of the left mouse click
   color             m_area_color;
   int               m_area_zorder;
   //--- Arrays for unique properties of buttons
   bool              m_buttons_state[];
   int               m_buttons_x_gap[];
   int               m_buttons_y_gap[];
   int               m_buttons_width[];
   string            m_buttons_text[];
   //--- Height of buttons
   int               m_button_y_size;
   //--- Icons for buttons in the active, disabled and blocked state
   string            m_icon_file_on;
   string            m_icon_file_off;
   string            m_icon_file_on_locked;
   string            m_icon_file_off_locked;
   //--- Text color
   color             m_text_color;
   color             m_text_color_off;
   color             m_text_color_hover;
   //--- Priority of left mouse click
   int               m_buttons_zorder;
   //---
public:
   //--- Setting up icons for the button in the active, disabled and blocked state
   void              IconFileOn(const string file_path)           { m_icon_file_on=file_path;         }
   void              IconFileOff(const string file_path)          { m_icon_file_off=file_path;        }
   void              IconFileOnLocked(const string file_path)     { m_icon_file_on_locked=file_path;  }
   void              IconFileOffLocked(const string file_path)    { m_icon_file_off_locked=file_path; }
   //--- (1) Background color, (2) text color
   void              AreaColor(const color clr)                   { m_area_color=clr;                 }
   void              TextColor(const color clr)                   { m_text_color=clr;                 }
   void              TextColorOff(const color clr)                { m_text_color_off=clr;             }
   void              TextColorHover(const color clr)              { m_text_color_hover=clr;           }

   //--- Adds a button with specified properties before creation
   void              AddButton(const int x_gap,const int y_gap,const string text,const int width);
  };

Then, we need to create arrays of instances of primitive object classes and methods for their creation. Similar to a group of simple buttons, radio buttons will be created in a loop. However, here the loop will be located in the main method and the index, which will participate in forming the name of each object, will be passed to the methods of creating those objects.

class CRadioButtons : public CElement
  {
private:
   //--- Objects for creating a button
   CRectLabel        m_area[];
   CBmpLabel         m_icon[];
   CLabel            m_label[];
   //---
public:
   //--- Methods for creating a button
   bool              CreateRadioButtons(const long chart_id,const int window,const int x,const int y);
   //---
private:
   bool              CreateArea(const int index);
   bool              CreateRadio(const int index);
   bool              CreateLabel(const int index);
   //---
public:
   //--- Number of buttons
   int               RadioButtonsTotal(void)                const { return(::ArraySize(m_icon));      }
  };
//+------------------------------------------------------------------+
//| Creates a group of the button objects                            |
//+------------------------------------------------------------------+
bool CRadioButtons::CreateRadioButtons(const long chart_id,const int window,const int x,const int y)
  {
//--- Leave, if there is no form pointer
   if(::CheckPointer(m_wnd)==POINTER_INVALID)
     {
      ::Print(__FUNCTION__," > Before creating a group of radio buttons, the class must be passed "
              "the form pointer: CButtonsGroup::WindowPointer(CWindow &object)");
      return(false);
     }
//--- Initializing variables
   m_id       =m_wnd.LastId()+1;
   m_chart_id =chart_id;
   m_subwin   =window;
   m_x        =x;
   m_y        =y;
//--- Get the number of buttons in the group
   int radio_buttons_total=RadioButtonsTotal();
//--- If there is no button in a group, report
   if(radio_buttons_total<1)
     {
      ::Print(__FUNCTION__," > This method is to be called "
              "if a group contains at least one button! Use the CRadioButtons::AddButton() method");
      return(false);
     }
//--- Set up a group of buttons
   for(int i=0; i<radio_buttons_total; i++)
     {
      CreateArea(i);
      CreateRadio(i);
      CreateLabel(i);
      //--- Zeroing the focus
      m_area[i].MouseFocus(false);
     }
//--- Hide the element if it is a dialog window or it is minimized
   if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized())
      Hide();
//---
   return(true);
  }

This class must also contain methods for changing the state of a control (available/blocked) and toggling the button state by the specified index. Here, these methods are slightly simpler than in the CButtonsGroup class because a group of radio items has only one mode. You can study these methods in the files attached to this article. In addition to that, this class requires methods for getting the text and index of the highlighted button like in the class of simple buttons.

class CButtonsGroup : public CElement
  {
private:
   //--- (1) Text and (2) index of the highlighted button
   string            m_selected_button_text;
   int               m_selected_button_index;
   //--- Available/blocked
   bool              m_buttons_group_state;
   //---
public:
   //--- General state of the button group (available/blocked)
   bool              ButtonsGroupState(void)                  const { return(m_buttons_group_state);   }
   void              ButtonsGroupState(const bool state);
   //--- Returns (1) the text and (2) index of the highlighted button
   string            SelectedButtonText(void)                 const { return(m_selected_button_text);  }
   int               SelectedButtonIndex(void)                const { return(m_selected_button_index); }
   //--- Toggles the button state by the specified index
   void              SelectionButton(const int index);
  };

We will use the new identifier ON_CLICK_LABEL for sending a message about the pressing of controls which look like a text label on the form. Add this to the Defines.mqh file:

#define ON_CLICK_LABEL            (10) // Pressing of the text label

The implementation of the method for handling of the pressing of the group buttons is shown below. After passing the following checks (1) for belonging to the control type, (2) for the identifier and (3) for the availability, the index of the pressed button will be identified by the exact name in a loop. If a button of this group was pressed and this button is not currently highlighted, toggling will be carried out and a message will be sent, which can be received in the custom class.

class CButtonsGroup : public CElement
  {
private:
   //--- Handling the pressing of the button
   bool              OnClickButton(const string pressed_object);
   //--- Getting the identifier from the name of the radio button
   int               IdFromObjectName(const string object_name);
  };
//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void CRadioButtons::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handling the left mouse button click event on the object
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Toggle the button state
      if(OnClickButton(sparam))
         return;
     }
  }
//+------------------------------------------------------------------+
//| Pressing of a radio button                                       |
//+------------------------------------------------------------------+
bool CRadioButtons::OnClickButton(const string pressed_object)
  {
//--- Leave, if the pressing was not on the menu item
   if(::StringFind(pressed_object,CElement::ProgramName()+"_radio_area_",0)<0)
      return(false);
//--- Get the identifier and index from the object name
   int id=IdFromObjectName(pressed_object);
//--- Leave, if the pressing was not of the item to which this context menu is attached
   if(id!=CElement::Id())
      return(false);
//--- For checking the index
   int check_index=WRONG_VALUE;
//--- Leave, if the buttons are blocked
   if(!m_radio_buttons_state)
      return(false);
//--- If the pressing took place, store the index
   int radio_buttons_total=RadioButtonsTotal();
   for(int i=0; i<radio_buttons_total; i++)
     {
      if(m_area[i].Name()==pressed_object)
        {
         check_index=i;
         break;
        }
     }
//--- Leave, if there was no pressing of a button in this group or
//    if it is an already highlighted radio button
   if(check_index==WRONG_VALUE || check_index==m_selected_button_index)
      return(false);
//--- Toggle the button state
   SelectionRadioButton(check_index);
//--- Send a signal about it
   ::EventChartCustom(m_chart_id,ON_CLICK_LABEL,CElement::Id(),check_index,m_selected_button_text);
   return(true);
  }

Now, everything is ready for testing. Add four groups of four radio buttons (CRadioButtons)and a group of simple buttons (CButtonsGroup) to the EA which was used for testing a group of simple buttons. 

class CProgram : public CWndEvents
  {
private:
   //--- Group of radio buttons 1
   CRadioButtons     m_radio_buttons1;
   //--- Group of simple buttons 2
   CButtonsGroup     m_buttons_group2;
   //--- Groups of radio buttons 2,3,4
   CRadioButtons     m_radio_buttons2;
   CRadioButtons     m_radio_buttons3;
   CRadioButtons     m_radio_buttons4;
   //---
private:
   //--- Group of radio buttons 1
#define RADIO_BUTTONS1_GAP_X     (7)
#define RADIO_BUTTONS1_GAP_Y     (75)
   bool              CreateRadioButtons1();
   //--- Group of simple buttons 2
#define BUTTONS_GROUP2_GAP_X     (7)
#define BUTTONS_GROUP2_GAP_Y     (100)
   bool              CreateButtonsGroup2(void);
   //--- Groups of radio buttons 2,3,4
#define RADIO_BUTTONS2_GAP_X     (7)
#define RADIO_BUTTONS2_GAP_Y     (125)
   bool              CreateRadioButtons2();
#define RADIO_BUTTONS3_GAP_X     (105)
#define RADIO_BUTTONS3_GAP_Y     (125)
   bool              CreateRadioButtons3();
#define RADIO_BUTTONS4_GAP_X     (203)
#define RADIO_BUTTONS4_GAP_Y     (125)
   bool              CreateRadioButtons4();
  };
//+------------------------------------------------------------------+
//| Creates the trading panel                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Creating a form for controls
//--- Creating controls:
//    Main menu
//--- Context menus
//--- Group of simple buttons 1
//--- Group of radio buttons 1
   if(!CreateRadioButtons1())
      return(false);
//--- Group of simple buttons 2
   if(!CreateButtonsGroup2())
      return(false);
   //--- Groups of radio buttons 2,3,4
   if(!CreateRadioButtons2())
      return(false);
   if(!CreateRadioButtons3())
      return(false);
   if(!CreateRadioButtons4())
      return(false);
//--- Redrawing of the chart
   m_chart.Redraw();
   return(true);
  }

We will used the implementation of the method for one of them as an example. The rest will differ only in parameter values. 

//+------------------------------------------------------------------+
//| Creates group of radio buttons 1                                 |
//+------------------------------------------------------------------+
bool CProgram::CreateRadioButtons1(void)
  {
//--- Pass the panel object
   m_radio_buttons1.WindowPointer(m_window);
//--- Coordinates
   int x =m_window.X()+RADIO_BUTTONS1_GAP_X;
   int y =m_window.Y()+RADIO_BUTTONS1_GAP_Y;
//--- Properties
   int    buttons_x_offset[] ={0,98,196};
   int    buttons_y_offset[] ={0,0,0};
   string buttons_text[]     ={"Radio Button 1","Radio Button 2","Radio Button 3"};
   int    buttons_width[]    ={92,92,92};
//---
   for(int i=0; i<3; i++)
      m_radio_buttons1.AddButton(buttons_x_offset[i],buttons_y_offset[i],buttons_text[i],buttons_width[i]);
//--- Create a group of buttons
   if(!m_radio_buttons1.CreateRadioButtons(m_chart_id,m_subwin,x,y))
      return(false);
//--- Highlight the second button in the group
   m_radio_buttons1.SelectedRadioButton(1);
//--- Block radio buttons
   m_radio_buttons1.RadioButtonsState(false);
//--- Add an object to the common array of object groups
   CWndContainer::AddToElementsArray(0,m_radio_buttons1);
   return(true);
  }

Let us create two buttons in the additional (second) group of buttons of the CButtonsGroup type. For the sake of illustration, let us arrange that the second button of this group will block the first groups of simple and radio buttons and the first one will make them available.

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Pressing of the button event
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      ::Print("id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
      //--- If the identifier of the second group or simple buttons and 
      //    the text of highlighted button in this group match the string parameter of the message
      if(lparam==m_buttons_group2.Id() && sparam==m_buttons_group2.SelectedButtonText())
        {
         //--- If this is the index of the first button, unblock specified controls
         if((int)dparam==0)
           {
            m_buttons_group1.ButtonsGroupState(true);
            m_radio_buttons1.RadioButtonsState(true);
           }
         //--- If this is the index of the second button, block specified controls
         else
           {
            m_buttons_group1.ButtonsGroupState(false);
            m_radio_buttons1.RadioButtonsState(false);
           }
        }
      return;
     }     
  }

Compile the files and load the program on to the chart. At this stage of development, you should get the result as shown in the screenshot below.

Fig. 4. Test of the radio button control.

Fig. 4. Test of the radio button control.


We have finished the development of the class for creating the group of radio buttons control. You can download a complete version in the files attached to this article. 

 


Developing the Class for Creating Groups of Icon Buttons

We have previously created the CIconButton class for creating the icon button control. Now, we are going to implement a control that will allow us to create a group of such buttons. Create the IconButtonsGroup.mqh file with the CIconButtonsGroup class where you can declare and implement standard virtual methods in the Controls folder containing previously created files with the classes of controls. Include this file in the WndContainer.mqh file of the library.

We will not describe this in detail as the CIconButtonsGroup class is essentially a composition of the methods that have been already considered in the CButtonsGroup and CRadioButtons class. Similar to the CRadioButtons class of radio buttons, the objects of the button group will be created with private methods in a loop of the main method for creating the control. The only parameter of these methods is the index that will be used for forming the names of graphical objects. We can arrange that the buttons of this group will change their background color and text when the cursor is hovering over them. 

Methods concerning the handling of events have already been discussed in this article. They are the same in this type of button group so we will show only the content of the CIconButtonsGroup class in the code below. The implementation of the methods that are outside of the class body can be found in the files attached to this article.

//+------------------------------------------------------------------+
//| Class for creating a group of icon buttons                       |
//+------------------------------------------------------------------+
class CIconButtonsGroup : public CElement
  {
private:
   //--- Pointer to the form to which the control is attached
   CWindow          *m_wnd;
   //--- Objects for creating a button
   CButton           m_buttons[];
   CBmpLabel         m_icons[];
   CLabel            m_labels[];
   //--- Gradients of text labels
   struct IconButtonsGradients
     {
      color             m_back_color_array[];
      color             m_label_color_array[];
     };
   IconButtonsGradients   m_icon_buttons_total[];
   //--- Button properties:
   //    Arrays for unique properties of buttons
   bool              m_buttons_state[];
   int               m_buttons_x_gap[];
   int               m_buttons_y_gap[];
   string            m_buttons_text[];
   int               m_buttons_width[];
   string            m_icon_file_on[];
   string            m_icon_file_off[];
   //--- Height of buttons
   int               m_buttons_y_size;
   //--- Background color in different modes
   color             m_back_color;
   color             m_back_color_off;
   color             m_back_color_hover;
   color             m_back_color_pressed;
   //--- Frame color
   color             m_border_color;
   color             m_border_color_off;
   //--- Icon margins
   int               m_icon_x_gap;
   int               m_icon_y_gap;
   //--- Text and text label margins
   int               m_label_x_gap;
   int               m_label_y_gap;
   //--- Text label color in different modes
   color             m_label_color;
   color             m_label_color_off;
   color             m_label_color_hover;
   color             m_label_color_pressed;
   //--- (1) Text and (2) index of the highlighted button
   string            m_selected_button_text;
   int               m_selected_button_index;
   //--- General priority of unclickable objects
   int               m_zorder;
   //--- Priority of left mouse click
   int               m_buttons_zorder;
   //--- Available/blocked
   bool              m_icon_buttons_state;
   //---
public:
                     CIconButtonsGroup(void);
                    ~CIconButtonsGroup(void);
   //--- Methods for creating a button
   bool              CreateIconButtonsGroup(const long chart_id,const int window,const int x,const int y);
   //---
private:
   bool              CreateButton(const int index);
   bool              CreateIcon(const int index);
   bool              CreateLabel(const int index);
   //---
public:
   //--- (1) Stores the form pointer, (2) button height, (3) number of buttons,
   //    (4) general state of the button (available/blocked)
   void              WindowPointer(CWindow &object)               { m_wnd=::GetPointer(object);      }
   void              ButtonsYSize(const int y_size)               { m_buttons_y_size=y_size;         }
   int               IconButtonsTotal(void)                 const { return(::ArraySize(m_icons));    }
   bool              IconButtonsState(void)                 const { return(m_icon_buttons_state);    }
   void              IconButtonsState(const bool state);
   //--- Button background colors
   void              BackColor(const color clr)                   { m_back_color=clr;                }
   void              BackColorOff(const color clr)                { m_back_color_off=clr;            }
   void              BackColorHover(const color clr)              { m_back_color_hover=clr;          }
   void              BackColorPressed(const color clr)            { m_back_color_pressed=clr;        }
   //--- Icon margins
   void              IconXGap(const int x_gap)                    { m_icon_x_gap=x_gap;              }
   void              IconYGap(const int y_gap)                    { m_icon_y_gap=y_gap;              }
   //--- Text label margins
   void              LabelXGap(const int x_gap)                   { m_label_x_gap=x_gap;             }
   void              LabelYGap(const int y_gap)                   { m_label_y_gap=y_gap;             }
   //--- Returns (1) the text and (2) index of the highlighted button
   string            SelectedButtonText(void)               const { return(m_selected_button_text);  }
   int               SelectedButtonIndex(void)              const { return(m_selected_button_index); }
   //--- Toggles the state of a radio button by the specified index
   void              SelectedRadioButton(const int index);

   //--- Adds a button with specified properties before creation
   void              AddButton(const int x_gap,const int y_gap,const string text,
                               const int width,const string icon_file_on,const string icon_file_off);
   //--- Changing the color
   void              ChangeObjectsColor(void);
   //---
public:
   //--- Chart event handler
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Timer
   virtual void      OnEventTimer(void);
   //--- Moving the control
   virtual void      Moving(const int x,const int y);
   //--- (1) Show, (2) hide, (3) reset, (4) delete
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) Setting, (2) resetting priorities of left mouse button click
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
   //---
private:
   //--- Handling the pressing of the button
   bool              OnClickButton(const string pressed_object);
   //--- Checking the pressed left mouse button over the group buttons
   void              CheckPressedOverButton(void);
   //--- Getting the identifier from the name of the radio button
   int               IdFromObjectName(const string object_name);
  };

Let us test this control. As an example, create a group of icon buttons on the form of the same EA where we previously tested groups of buttons of the CButtonsGroup and CRadioButtons type. For that, add the lines of the code to the custom class as shown in the code below.

class CProgram : public CWndEvents
  {
private:
   //--- Group of icon buttons 1
   CIconButtonsGroup m_icon_buttons_group1;
   //---
private:
   //--- Group of icon buttons 1
#define IBUTTONS_GROUP1_GAP_X    (7)
#define IBUTTONS_GROUP1_GAP_Y    (190)
   bool              CreateIconButtonsGroup1(void);
  };
//+------------------------------------------------------------------+
//| Creates the trading panel                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Creating a form for controls
//--- Creating controls:
//    Main menu
//--- Context menus
//--- Group of simple buttons 1
//--- Group of radio buttons 1
//--- Group of simple buttons 2
//--- Groups of radio buttons 2,3,4
//--- Group of icon buttons 1
   if(!CreateIconButtonsGroup1())
      return(false);
//--- Redrawing of the chart
   m_chart.Redraw();
   return(true);
  }

Compile the files and load the program to the chart. You should get the result as shown in the screenshot below.

Fig. 5. Test of the group of icon buttons.

Fig. 5. Test of the group of icon buttons.


The development of the class for creating the group of icon buttons control is completed. You can download a complete version in the files attached to this article.

 


Conclusion

This is the end of the third part of the series about developing the library for creating graphical interfaces in the MetaTrader trading terminals. Currently, the library structure looks as shown in the schematic below.

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

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


In the next (fourth) part of the series, we will continue developing the library. We will consider the following topics: 

The archives below with library files at the current stage of development, pictures and files of the programs considered in this article can be downloaded for tests in the MetaTrader terminals. If you have questions on using material from those files, you can refer to the detailed description of the library development in one of the articles from the list below or ask your question in the comments of this article. 

List of articles (chapters of the third part: