Русский 中文 Español Deutsch 日本語 Português
Graphical Interfaces VI: the Slider and the Dual Slider Controls (Chapter 2)

Graphical Interfaces VI: the Slider and the Dual Slider Controls (Chapter 2)

MetaTrader 5Examples | 8 June 2016, 16:00
12 439 1
Anatoli Kazharski
Anatoli Kazharski

Contents

 

Introduction

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

In the previous article we enriched our library with four controls frequently used in graphical interfaces: checkbox, edit, edit with checkbox and check combobox. The second chapter of the sixth part will be dedicated to the slider and the dual slider controls.


The Slider Control

Slider is a type of the edit control which contains a range limited by the minimum and maximum values. Unlike the edit control that we considered in detail in the previous article, a slider does not have buttons for changing the value in the entry field. For that purpose, there is a line along which the slider can be moved. Such an interface element is suitable for the cases when only an approximate value within a certain range is required and the precise value is not necessary. However, there is still a possibility to enter a precise value.

This element will be composed of six graphical objects. They are:

  1. Background
  2. Caption (text label)
  3. Entry field
  4. Slider line
  5. Slider runner
  6. Slider indicator

 

Fig. 1. Compound parts of the slider control.


Let us study the class of this control in detail.

 


Developing a Class for Creating the Slider Control

Create the Slider.mqh file and include it in the WndContainer.mqh file:

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Slider.mqh"

In the Slider.mqh file create the CSlider class with the standard set of methods that must be present in every control of the library under development. Alongside included files Element.mqh and Window.mqh, include the SeparateLine.mqh file with the CSeparateLine class for creating a separation line. The CSeparateLine class was already discussed in the article Graphical Interfaces II: the Separation Line and Context Menu Elements (Chapter 2) and therefore we are not going to consider it again. The only thing I would like to remind you of is that if the set height is greater than two pixels, there is an empty space between two drawn lines. Visually it looks like a hollow which is suitable for creating a slider line (slit or slot) where the slider runner will be moving.

//+------------------------------------------------------------------+
//|                                                       Slider.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
#include "SeparateLine.mqh"
//+------------------------------------------------------------------+
//| Class for creating a slider with edit                            |
//+------------------------------------------------------------------+
class CSlider : public CElement
  {
private:
   //--- Pointer to the form to which the element is attached
   CWindow          *m_wnd;
public:
                     CSlider(void);
                    ~CSlider(void);
   //---
public:
   //--- Stores the form pointer
   void              WindowPointer(CWindow &object)                 { m_wnd=::GetPointer(object);         }
   //---
public:
   //--- Chart event handler
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Timer
   virtual void      OnEventTimer(void);
   //--- Moving the element
   virtual void      Moving(const int x,const int y);
   //--- (1) Show, (2) hide, (3) reset, (4) delete
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) Set, (2) reset priorities of the left mouse button press
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
   //--- Reset the color
   virtual void      ResetColors(void);
  };

The library user should have a possibility to set the properties of all the objects the slider control will be composed of. These properties are listed below.

  • Color of the element background
  • Text for describing the slider
  • Colors of the text label in different states
  • Current value in the entry field
  • Size of the entry field
  • Colors of the entry field in different states
  • Colors of the text of the entry field in different states
  • Colors of the frame of the entry field in different states
  • Size of the slit along the Y axis (height)
  • Colors of the slit lines
  • Colors of the slider indicator in different states
  • Size of the slider runner
  • Colors of the slider runner
  • Priorities of the left mouse button press

Below is the code of fields and methods of the class of the element objects properties listed above: 

class CSlider : public CElement
  {
private:
   //--- Color of the element background
   color             m_area_color;
   //--- Text for describing the slider
   string            m_label_text;
   //--- Colors of the text label in different states
   color             m_label_color;
   color             m_label_color_hover;
   color             m_label_color_locked;
   color             m_label_color_array[];
   //--- Size of the entry field
   int               m_edit_x_size;
   int               m_edit_y_size;
   //--- Colors of the entry field in different states
   color             m_edit_color;
   color             m_edit_color_locked;
   //--- Colors of the text of the entry field in different states
   color             m_edit_text_color;
   color             m_edit_text_color_locked;
   //--- Colors of the frame of the entry field in different states
   color             m_edit_border_color;
   color             m_edit_border_color_hover;
   color             m_edit_border_color_locked;
   color             m_edit_border_color_array[];
   //--- Size of the slit
   int               m_slot_y_size;
   //--- Colors of the slit
   color             m_slot_line_dark_color;
   color             m_slot_line_light_color;
   //--- Colors of the indicator in different states
   color             m_slot_indicator_color;
   color             m_slot_indicator_color_locked;
   //--- Size of the slider runner
   int               m_thumb_x_size;
   int               m_thumb_y_size;
   //--- Colors of the slider runner
   color             m_thumb_color;
   color             m_thumb_color_hover;
   color             m_thumb_color_locked;
   color             m_thumb_color_pressed;
   //--- Priorities of the left mouse button press
   int               m_zorder;
   int               m_area_zorder;
   int               m_edit_zorder;
   //---
public:
   //--- (1) Background color, (2) colors of the text label
   void              AreaColor(const color clr)                     { m_area_color=clr;                   }
   void              LabelColor(const color clr)                    { m_label_color=clr;                  }
   void              LabelColorHover(const color clr)               { m_label_color_hover=clr;            }
   void              LabelColorLocked(const color clr)              { m_label_color_locked=clr;           }
   //--- Size of (1) the entry field and (2) the slit
   void              EditXSize(const int x_size)                    { m_edit_x_size=x_size;               }
   void              EditYSize(const int y_size)                    { m_edit_y_size=y_size;               }
   void              SlotYSize(const int y_size)                    { m_slot_y_size=y_size;               }
   //--- Colors of the entry field in different states
   void              EditColor(const color clr)                     { m_edit_color=clr;                   }
   void              EditColorLocked(const color clr)               { m_edit_color_locked=clr;            }
   //--- Colors of the text of the entry field in different states
   void              EditTextColor(const color clr)                 { m_edit_text_color=clr;              }
   void              EditTextColorLocked(const color clr)           { m_edit_text_color_locked=clr;       }
   //--- Colors of the frame of the entry field in different states
   void              EditBorderColor(const color clr)               { m_edit_border_color=clr;            }
   void              EditBorderColorHover(const color clr)          { m_edit_border_color_hover=clr;      }
   void              EditBorderColorLocked(const color clr)         { m_edit_border_color_locked=clr;     }
   //--- (1) Dark and (2) light color of the separation line (slit)
   void              SlotLineDarkColor(const color clr)             { m_slot_line_dark_color=clr;         }
   void              SlotLineLightColor(const color clr)            { m_slot_line_light_color=clr;        }
   //--- Colors of the slider indicator in different states
   void              SlotIndicatorColor(const color clr)            { m_slot_indicator_color=clr;         }
   void              SlotIndicatorColorLocked(const color clr)      { m_slot_indicator_color_locked=clr;  }
   //--- Size of the slider runner
   void              ThumbXSize(const int x_size)                   { m_thumb_x_size=x_size;              }
   void              ThumbYSize(const int y_size)                   { m_thumb_y_size=y_size;              }
   //--- Colors of the slider runner
   void              ThumbColor(const color clr)                    { m_thumb_color=clr;                  }
   void              ThumbColorHover(const color clr)               { m_thumb_color_hover=clr;            }
   void              ThumbColorLocked(const color clr)              { m_thumb_color_locked=clr;           }
   void              ThumbColorPressed(const color clr)             { m_thumb_color_pressed=clr;          }
  };

The properties from the previous list relate mainly to the color and the size of the element objects. Properties to do with the range and the slider entry field will make a separate group. These properties are: 

  • Minimum value
  • Maximum value
  • Step of changing values in the entry field
  • Mode of text alignment
  • Number of decimal places
class CSlider : public CElement
  {
private:
   //--- (1) Minimum and (2) maximum value, (3) step for changing the value
   double            m_min_value;
   double            m_max_value;
   double            m_step_value;
   //--- Number of decimal places
   int               m_digits;
   //--- Mode of text alignment
   ENUM_ALIGN_MODE   m_align_mode;
   //---
public:
   //--- Minimum value
   double            MinValue(void)                           const { return(m_min_value);                }
   void              MinValue(const double value)                   { m_min_value=value;                  }
   //--- Maximum value
   double            MaxValue(void)                           const { return(m_max_value);                }
   void              MaxValue(const double value)                   { m_max_value=value;                  }
   //--- Step of changing the value
   double            StepValue(void)                          const { return(m_step_value);               }
   void              StepValue(const double value)                  { m_step_value=(value<=0)? 1 : value; }
   //--- (1) Number of decimal places, (2) mode of text alignment
   void              SetDigits(const int digits)                    { m_digits=::fabs(digits);            }
   void              AlignMode(ENUM_ALIGN_MODE mode)                { m_align_mode=mode;                  }
  };

To get the current value as well as to adjust and set the new value in the entry field, we will use the CSlider::GetValue(), CSlider::SetValue() and CSlider::ChangeValue() methods:

class CSlider : public CElement
  {
private:
   //--- Current value in the entry field
   double            m_edit_value;
   //---
public:
   //--- Returning and setting the value in the entry field
   double            GetValue(void)                           const { return(m_edit_value);               }
   bool              SetValue(const double value);
   //--- Changing the value in the entry field
   void              ChangeValue(const double value);
  };
//+------------------------------------------------------------------+
//| Setting the current value                                        |
//+------------------------------------------------------------------+
bool CSlider::SetValue(const double value)
  {
//--- For adjustment
   double corrected_value=0.0;
//--- Adjust considering the step
   corrected_value=::MathRound(value/m_step_value)*m_step_value;
//--- Check for the minimum/maximum
   if(corrected_value<=m_min_value)
      corrected_value=m_min_value;
   if(corrected_value>=m_max_value)
      corrected_value=m_max_value;
//--- If the value has been changed
   if(m_edit_value!=corrected_value)
     {
      m_edit_value=corrected_value;
      return(true);
     }
//--- Value unchanged
   return(false);
  }
//+------------------------------------------------------------------+
//| Changing the value in the entry field                            |
//+------------------------------------------------------------------+
void CSlider::ChangeValue(const double value)
  {
//--- Check, adjust and store the new value
   SetValue(value);
//--- Set the new value in the entry field
   m_edit.Description(::DoubleToString(GetValue(),m_digits));
  }

When the slider runner is moving, the value in the entry field must be calculated in relation to the X coordinate. If the value is entered manually, then the X coordinate of the slider runner must be calculated in relation to the new value in the entry field. In other words, the possibility of inverse transformation should be allowed when the element is developed. 

For correct calculation, we will need auxiliary fields (variables) of the class that will be used in the calculation. These variables will have to be calculated (initialized) only once, immediately after the element has been created. Below are the description of these variables.

  • Number of pixels in the working area (m_pixels_total).
  • Number of steps in the value range of the working area (m_value_steps_total).
  • Step in relation to the width of the working area (m_position_step).

Let us write a method for calculating these values and call it CSlider::CalculateCoefficients():

class CSlider : public CElement
  {
private:
   //--- Number of pixels in the working area
   int               m_pixels_total;
   //--- Number of steps in the working area
   int               m_value_steps_total;
   //--- Step of transformed width of the working area
   double            m_position_step;
   //---
private:
   //--- Calculation of values (steps and coefficients)
   bool              CalculateCoefficients(void);
  };
//+------------------------------------------------------------------+
//| Calculation of values (steps and coefficients)                   |
//+------------------------------------------------------------------+
bool CSlider::CalculateCoefficients(void)
  {
//--- Leave, if the width of the element is less than the width of the slider runner
   if(CElement::XSize()<m_thumb_x_size)
      return(false);
//--- Number of pixels in the working area
   m_pixels_total=CElement::XSize()-m_thumb_x_size;
//--- Number of steps in the value range of the working area
   m_value_steps_total=int((m_max_value-m_min_value)/m_step_value);
//--- Step in relation to the width of the working area
   m_position_step=m_step_value*(double(m_value_steps_total)/double(m_pixels_total));
   return(true);
  }

Now, the values of the variables listed above can be used for calculating the X coordinate of the slider runner in relation to the value in the entry field and back. For that, let us write two separate methods CSlider::CalculateThumbX() and CSlider::CalculateThumbPos().

At the beginning of the CSlider::CalculateThumbX() method, we will calculate the values of the auxiliary local variable (neg_range) for adjustment in case the value of the minimum limit is negative. Then, we will calculate the X coordinate for the slider runner. After that, in case the line of the slider has been exceeded, the value is adjusted. At the end of the method, a new value of the X coordinate is set for the slider runner and the margin from the edge of the form to which the element is attached is calculated anew.

At the very beginning of the CSlider::CalculateThumbPos() method, we get the position of the slider runner in the range of values. Then, an adjustment is carried out if the value of the minimum limit is negative and the value of the m_current_pos_x variable is correct. Then, if the working area has been exceeded, a corresponding adjustment of value is performed.

class CSlider : public CElement
  {
private:
   //--- Current position of the slider runner: (1) value, (2) the X coordinate
   double            m_current_pos;
   double            m_current_pos_x;
   //---
private:
   //--- Calculation of the X coordinate of the slider runner
   void              CalculateThumbX(void);
   //--- Changes the current position of the slider runner in relation to the current value
   void              CalculateThumbPos(void);
  };
//+------------------------------------------------------------------+
//| Calculating the X coordinate of the slider runner                |
//+------------------------------------------------------------------+
void CSlider::CalculateThumbX(void)
  {
//--- Adjustment considering that the minimum value can be negative
   double neg_range=(m_min_value<0)? ::fabs(m_min_value/m_position_step) : 0;
//--- Calculate the X coordinate for the slider runner
   m_current_pos_x=m_area.X()+(m_edit_value/m_position_step)+neg_range;
//--- If the working area is exceeded on the left
   if(m_current_pos_x<m_area.X())
      m_current_pos_x=m_area.X();
//--- If the working area is exceeded on the right
   if(m_current_pos_x+m_thumb.XSize()>m_area.X2())
      m_current_pos_x=m_area.X2()-m_thumb.XSize();
//--- Store and set the new X coordinate
   m_thumb.X(int(m_current_pos_x));
   m_thumb.X_Distance(int(m_current_pos_x));
   m_thumb.XGap(m_thumb.X()-m_wnd.X());
  }
//+------------------------------------------------------------------+
//| Calculation of the slider runner position in the value range     |
//+------------------------------------------------------------------+
void CSlider::CalculateThumbPos(void)
  {
//--- Get the position number of the slider runner
   m_current_pos=(m_thumb.X()-m_area.X())*m_position_step;
//--- Adjustment considering that the minimum value can be negative
   if(m_min_value<0 && m_current_pos_x!=WRONG_VALUE)
      m_current_pos+=int(m_min_value);
//--- Check for exceeding the working area on the right/left
   if(m_thumb.X2()>=m_area.X2())
      m_current_pos=int(m_max_value);
   if(m_thumb.X()<=m_area.X())
      m_current_pos=int(m_min_value);
  }

When the slider runner is moving, the width of the slider indicator must be calculated and updated. The right side of the slider indicator must be tied to the slider runner. For that, let us write the CSlider::UpdateIndicator() method:

class CSlider : public CElement
  {
private:
   //--- Updating the slider indicator
   void              UpdateIndicator(void);
  };
//+------------------------------------------------------------------+
//| Updating the slider indicator                                    |
//+------------------------------------------------------------------+
void CSlider::UpdateIndicator(void)
  {
//--- Calculate the size
   int x_size=m_thumb.X()-m_indicator.X();
//--- Adjustment in case of impermissible values
   if(x_size<=0)
      x_size=1;
//--- Setting a new size
   m_indicator.X_Size(x_size);
  }

To create the slider control we will need six private and one public methods.

class CSlider : public CElement
  {
public:
   //--- Methods for creating the control
   bool              CreateSlider(const long chart_id,const int subwin,const string text,const int x,const int y);
   //---
private:
   bool              CreateArea(void);
   bool              CreateLabel(void);
   bool              CreateEdit(void);
   bool              CreateSlot(void);
   bool              CreateIndicator(void);
   bool              CreateThumb(void);
  };

We are going to discuss only the code of the CSlider::CreateThumb() method because this is where all methods for calculations considered before will be called. Other methods for creating the control objects do not contain anything that was not spoken about in the previous articles of this series.

//+------------------------------------------------------------------+
//| Creates the slider runner                                        |
//+------------------------------------------------------------------+
bool CSlider::CreateThumb(void)
  {
//--- Forming the object name
   string name=CElement::ProgramName()+"_slider_thumb_"+(string)CElement::Id();
//--- Coordinates
   int x=CElement::X();
   int y=m_slot.Y()-((m_thumb_y_size-m_slot_y_size)/2);
//--- Set the object
   if(!m_thumb.Create(m_chart_id,name,m_subwin,x,y,m_thumb_x_size,m_thumb_y_size))
      return(false);
//--- set properties
   m_thumb.Color(m_thumb_color);
   m_thumb.BackColor(m_thumb_color);
   m_thumb.BorderType(BORDER_FLAT);
   m_thumb.Corner(m_corner);
   m_thumb.Selectable(false);
   m_thumb.Z_Order(m_zorder);
   m_thumb.Tooltip("\n");
//--- Store the size (in the object)
   m_thumb.XSize(m_thumb.X_Size());
   m_thumb.YSize(m_thumb.Y_Size());
//--- Store coordinates
   m_thumb.X(x);
   m_thumb.Y(y);
//--- Margins from the edge
   m_thumb.XGap(x-m_wnd.X());
   m_thumb.YGap(y-m_wnd.Y());
//--- Calculation of the values of auxiliary variables
   CalculateCoefficients();
//--- Calculation of the X coordinates of the slider runner in relation to the current value in the entry field
   CalculateThumbX();
//--- Calculation of the slider runner position in the value range
   CalculateThumbPos();
//--- Update the slider indicator
   UpdateIndicator();
//--- Store the object pointer
   CElement::AddToArray(m_thumb);
   return(true);
  }

Methods of moving the slider runner are identical to the similarly-named methods in the CScroll and CScrollH classes, which were discussed in detail in the article Graphical Interfaces V: The Vertical and Horizontal Scrollbar (Chapter 1). This is why we are not going to consider their code here. We will only declare them in the body of the CSlider class. 

class CSlider : public CElement
  {
private:
   //--- State of the mouse button (pressed/released)
   ENUM_THUMB_MOUSE_STATE m_clamping_area_mouse;
   //--- To identify the mode of the slider runner movement
   bool              m_slider_thumb_state;
   //--- Variables connected with the slider runner movement
   int               m_slider_size_fixing;
   int               m_slider_point_fixing;
   //---
private:
   //--- Process of the slider runner movement
   void              OnDragThumb(const int x);
   //--- Updating of the slider runner location
   void              UpdateThumb(const int new_x_point);
   //--- Checks the state of the mouse button
   void              CheckMouseButtonState(void);
   //--- Zeroing variables connected with the slider runner movement
   void              ZeroThumbVariables(void);
  };

To handle entering the values in the entry field, create the CSlider::OnEndEdit() method that will be called in the CSlider::OnEvent() event handler. 

At the beginning of the CSlider::OnEndEdit()method, there is a check by the object name for if a value was entered in the field of this slider. Then, we get the current value of the entry field. Then, follow a mandatory check for the impermissible value entry, adjustment, calculation of the X coordinate of the slider runner and position in the value range. After that, the slider indicator is updated. At the end of this method, a message must be sent with (1) the ON_END_EDIT custom event identifier, (2) the element identifier, (3) the element index and (4) the description contained in the text label. 

class CSlider : public CElement
  {
private:
   //--- Handling entering the value in the entry field
   bool              OnEndEdit(const string object_name);
  };
//+------------------------------------------------------------------+
//| Handling entering the value in the entry field                   |
//+------------------------------------------------------------------+
bool CSlider::OnEndEdit(const string object_name)
  {
//--- Leave, if the object name is different
   if(object_name!=m_edit.Name())
      return(false);
//--- Get the entered value
   double entered_value=::StringToDouble(m_edit.Description());
//--- Check, adjust and store the new value
   ChangeValue(entered_value);
//--- Calculate the X coordinate of the slider runner
   CalculateThumbX();
//--- Calculate the position in the value range
   CalculateThumbPos();
//--- Update the slider indicator
   UpdateIndicator();
//--- Send a message about it
   ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),CElement::Index(),m_label.Description());
   return(true);
  }

Same custom message must be sent after the slider runner stops moving for changing the value in the entry field. The CSlider::ZeroThumbVariables() method serves this purpose best of all. This is called in the CSlider::CheckMouseButtonState() method where the area of the left mouse button press is tracked. Call of the CSlider::ZeroThumbVariables() method already implies that the left mouse button is released. If it is pressed down over the area of the slider runner it means that the movement of the slider runner is finished and a message must be sent that the value in the entry field has been changed.

//+------------------------------------------------------------------+
//| Zeroing variables related to the movement of the scrollbar       |
//+------------------------------------------------------------------+
void CSlider::ZeroThumbVariables(void)
  {
//--- If you are here, it means that the left mouse button is released.
//    If the left mouse button was pressed over the slider runner...
   if(m_clamping_area_mouse==THUMB_PRESSED_INSIDE)
     {
      //--- ... send a message that changing of the value in the entry field with the sider runner is completed
      ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),CElement::Index(),m_label.Description());
     }
//---
   m_slider_size_fixing  =0;
   m_clamping_area_mouse =THUMB_NOT_PRESSED;
//--- If the element identifier matches the activating identifier,
//    unblock the form and reset the identifier of the activated element
   if(CElement::Id()==m_wnd.IdActivatedElement())
     {
      m_wnd.IsLocked(false);
      m_wnd.IdActivatedElement(WRONG_VALUE);
     }
  }

Complete code of the CSlider::OnEvent() event handler in this case will look as follows:

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void CSlider::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handling of the cursor movement event
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Leave, if the element is hidden     
      if(!CElement::IsVisible())
         return;
      //--- Coordinates and the state of the left mouse button
      int x=(int)lparam;
      int y=(int)dparam;
      m_mouse_state=(bool)int(sparam);
      //--- Checking the focus over elements
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      m_thumb.MouseFocus(x>m_thumb.X() && x<m_thumb.X2() && 
                         y>m_thumb.Y() && y<m_thumb.Y2());
      //--- Leave, if the element is blocked
      if(!m_slider_state)
         return;
      //--- Check and store the state of the mouse button
      CheckMouseButtonState();
      //--- Change the color of the slider runner
      ChangeThumbColor();
      //--- If management was passed to the slider line, identify its location
      if(m_clamping_area_mouse==THUMB_PRESSED_INSIDE)
        {
         //--- Moving the slider runner
         OnDragThumb(x);
         //--- Calculation of the slider runner position in the value range
         CalculateThumbPos();
         //--- Setting a new value in the entry field
         ChangeValue(m_current_pos);
         //--- Update the slider indicator
         UpdateIndicator();
         return;
        }
     }
//--- Handling of the changing the value in the entry field event
   if(id==CHARTEVENT_OBJECT_ENDEDIT)
     {
      //--- Handling of the value entry
      if(OnEndEdit(sparam))
         return;
     }
  }

We have implemented methods for creating and managing the slider element. Let us test it in the MQL application that we worked with in the previous article. 

 


Testing the Slider Control

In the previous article, in the test application we created four checkboxes that managed availability of other elements. We will add the slider element and the fifth checkbox for managing its availability to the graphical interface. For that, in the CProgram custom class, declare instances of the CCheckBox and CSlider classes as well as methods with margins from the edge of the form to which these controls will be attached.

//+------------------------------------------------------------------+
//| Class for creating the application                               |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- Checkboxes
   CCheckBox         m_checkbox5;
   //--- Sliders
   CSlider           m_slider1;
   //---
private:
   //--- Checkboxes
#define CHECKBOX5_GAP_X       (7)
#define CHECKBOX5_GAP_Y       (200)
   bool              CreateCheckBox5(const string text);
   //--- Sliders
#define SLIDER1_GAP_X         (32)
#define SLIDER1_GAP_Y         (225)
   bool              CreateSlider1(const string text);
  };

We considered code of the methods for creating checkboxes in the previous article, this is why we are moving on straight to the method for creating the CProgram::CreateSlider1() element. Using the CSlider::MinValue() and CSlider::MaxValue() methods, set a value range from -1 to 1. Set the step up to the 8th decimal place (0.00000001). The availability will depend on the current state of the fifth checkbox.

//+------------------------------------------------------------------+
//| Creates slider 1                                                 |
//+------------------------------------------------------------------+
bool CProgram::CreateSlider1(const string text)
  {
//--- Store the window pointer
   m_slider1.WindowPointer(m_window1);
//--- Coordinates
   int x=m_window1.X()+SLIDER1_GAP_X;
   int y=m_window1.Y()+SLIDER1_GAP_Y;
//--- Value
   double v=(m_slider1.GetValue()==WRONG_VALUE) ? 0.84615385 : m_slider1.GetValue();
//--- Set properties before creation
   m_slider1.XSize(264);
   m_slider1.YSize(40);
   m_slider1.EditXSize(87);
   m_slider1.MaxValue(1);
   m_slider1.StepValue(0.00000001);
   m_slider1.MinValue(-1);
   m_slider1.SetDigits(8);
   m_slider1.SetValue(v);
   m_slider1.AreaColor(clrWhiteSmoke);
   m_slider1.LabelColor(clrBlack);
   m_slider1.LabelColorLocked(clrSilver);
   m_slider1.EditColorLocked(clrWhiteSmoke);
   m_slider1.EditBorderColor(clrSilver);
   m_slider1.EditBorderColorLocked(clrSilver);
   m_slider1.EditTextColorLocked(clrSilver);
   m_slider1.SlotLineDarkColor(clrSilver);
   m_slider1.SlotLineLightColor(clrWhite);
   m_slider1.SlotYSize(4);
   m_slider1.ThumbColorLocked(clrLightGray);
   m_slider1.ThumbColorPressed(clrSilver);
   m_slider1.SlotIndicatorColor(C'85,170,255');
   m_slider1.SlotIndicatorColorLocked(clrLightGray);
//--- Create control
   if(!m_slider1.CreateSlider(m_chart_id,m_subwin,text,x,y))
      return(false);
//--- Availability will depend on the current state of the fifth checkbox
   m_slider1.SliderState(m_checkbox5.CheckButtonState());
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(0,m_slider1);
   return(true);
  }

New methods for creating elements must be called in the main method for creating the graphical interface. Below is the code of a shortened version of this method:

//+------------------------------------------------------------------+
//| Creates the trading panel                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Creating form 1 for controls
//--- Creating controls:
//    Main menu
//--- Context menus
//--- Creating the status bar
//--- Checkboxes
   if(!CreateCheckBox5("Checkbox 5"))
      return(false);
//--- Sliders
   if(!CreateSlider1("Slider 1:"))
      return(false);
//--- Redrawing the chart
   m_chart.Redraw();
   return(true);
  }

We will track the change of the state of the fifth checkbox for controlling the availability of the slider in the CProgram::OnEvent() event handler of the application. The event with the ON_END_EDIT custom identifier will indicate when the value in the entry field will change.

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- The text label press event
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LABEL)
     {
      ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
      //--- If the fifth checkbox was clicked on
      if(lparam==m_checkbox5.Id())
        {
         //--- Set the state for the first slider
         m_slider1.SliderState(m_checkbox5.CheckButtonState());
        }
     }
//--- The end of entering the value in the entry field event
   if(id==CHARTEVENT_CUSTOM+ON_END_EDIT)
     {
      ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
     }
  }

Now, the program can be compiled and loaded on to the chart. Try to interact with controls of the graphical interface of the application. If everything was done correctly, you will see the result as in the screenshot below:

 Fig. 2. Testing the slider control.

Fig. 2. Testing the slider control. 

 


The Dual Slider Control

The difference between a dual slider and a simple slider is that the former allows selection in the value range set by the user. For that reason, there are two slider runners on the slider line. The left slider runner can be moved from the left side of the slider line to the right slider runner. The right slider runner can be moved from the right side of the slider line to the left slider runner. A dual slider also has two entry fields reflecting the value in relation to the position of slider runners in the slider line. Values can be entered in these entry fields manually. This will change the position of the slider runners.

This control will be composed of eight primitive graphical objects. They are:

  1. Background.
  2. Caption (text label).
  3. Left entry field.
  4. Right entry field.
  5. Slider line.
  6. Left slider runner.
  7. Right slider runner.
  8. Slider indicator.


Fig. 3. Compound parts of the dual slider control.


Let us take a look at the class of a dual slider and note its difference from a simple slider.

 


Developing a Class for Creating the Dual Slider Control

Include the DualSlider.mqh file with the CDualSlider class of control in the WndContainer.mqh file:

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "DualSlider.mqh"

In terms of the control properties, the CDualSlider class is identical to the CSlider class. The only difference here is that the left and right entry fields and slider runners require separate fields and methods. Differences between these two methods are insignificant and this is why we are not going to discuss their code here. You can find it in the files attached to this article.

class CDualSlider : public CElement
  {
private:
   //--- Current values in the entry fields (left and right)
   double            m_left_edit_value;
   double            m_right_edit_value;
   //--- Current position of the slider runners (left and right)
   double            m_left_current_pos;
   double            m_left_current_pos_x;
   double            m_right_current_pos;
   double            m_right_current_pos_x;
   //--- State of the mouse button (pressed/released) for slider runners (left and right)
   ENUM_THUMB_MOUSE_STATE m_clamping_mouse_left_thumb;
   ENUM_THUMB_MOUSE_STATE m_clamping_mouse_right_thumb;
   //---
public:
   //--- Return and set the value in entry fields (left and right)
   double            GetLeftValue(void)                       const { return(m_left_edit_value);          }
   double            GetRightValue(void)                      const { return(m_right_edit_value);         }
   bool              SetLeftValue(double value);
   bool              SetRightValue(double value);
   //--- Changing values in entry fields (left and right)
   void              ChangeLeftValue(const double value);
   void              ChangeRightValue(const double value);
   //---
private:
   //--- Process of moving slider runners (left and right)
   void              OnDragLeftThumb(const int x);
   void              OnDragRightThumb(const int x);
   //--- Updating position of slider runners (left and right)
   void              UpdateLeftThumb(const int new_x_point);
   void              UpdateRightThumb(const int new_x_point);
   //--- Checks the state of the left mouse button over the slider runner
   void              CheckMouseOnLeftThumb(void);
   void              CheckMouseOnRightThumb(void);
   //--- Calculating the X coordinate of sliders (left and right)
   void              CalculateLeftThumbX(void);
   void              CalculateRightThumbX(void);
   //--- Changes position of the left slider runner in relation to the value (left and right)
   void              CalculateLeftThumbPos(void);
   void              CalculateRightThumbPos(void);
  };

Here, we will present only code of the methods where two entry fields and slider runners are considered. Below is the structure of the CDualSlider::OnEndEdit() method:

class CDualSlider : public CElement
  {
private:
   //--- Handling entering the value in the entry field
   bool              OnEndEdit(const string object_name);
  };
//+------------------------------------------------------------------+
//| End of entering the value                                        |
//+------------------------------------------------------------------+
bool CDualSlider::OnEndEdit(const string object_name)
  {
//--- If the value is entered in the left entry field
   if(object_name==m_left_edit.Name())
     {
      //--- Get the entered value
      double entered_value=::StringToDouble(m_left_edit.Description());
      //--- Check, adjust and store the new value
      ChangeLeftValue(entered_value);
      //--- Calculate the X coordinate of the slider runner
      CalculateLeftThumbX();
      //--- Updating of the slider runner location
      UpdateLeftThumb(m_left_thumb.X());
      //--- Calculate the position in the value range
      CalculateLeftThumbPos();
      //--- Check, adjust and store the new value
      ChangeLeftValue(m_left_current_pos);
      //--- Update the slider indicator
      UpdateIndicator();
      //--- Send a message about it
      ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),CElement::Index(),m_label.Description());
      return(true);
     }
//--- If the value is entered in the right entry field
   if(object_name==m_right_edit.Name())
     {
      //--- Get the entered value
      double entered_value=::StringToDouble(m_right_edit.Description());
      //--- Check, adjust and store the new value
      ChangeRightValue(entered_value);
      //--- Calculate the X coordinate of the slider runner
      CalculateRightThumbX();
      //--- Updating of the slider runner location
      UpdateRightThumb(m_right_thumb.X());
      //--- Calculate the position in the value range
      CalculateRightThumbPos();
      //--- Check, adjust and store the new value
      ChangeRightValue(m_right_current_pos);
      //--- Update the slider indicator
      UpdateIndicator();
      //--- Send a message about it
      ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),CElement::Index(),m_label.Description());
      return(true);
     }
//---
   return(false);
  }

The same applies to moving the left and right slider runners. The CDualSlider::OnEvent() event handler contains separate checks and blocks of code for each of them: 

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void CDualSlider::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handling of the cursor movement event
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Leave, if the element is hidden   
      if(!CElement::IsVisible())
         return;
      //--- Coordinates and the state of the left mouse button
      int x=(int)lparam;
      int y=(int)dparam;
      m_mouse_state=(bool)int(sparam);
      //--- Checking the focus over elements
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      m_left_thumb.MouseFocus(x>m_left_thumb.X() && x<m_left_thumb.X2() && 
                              y>m_left_thumb.Y() && y<m_left_thumb.Y2());
      m_right_thumb.MouseFocus(x>m_right_thumb.X() && x<m_right_thumb.X2() && 
                               y>m_right_thumb.Y() && y<m_right_thumb.Y2());
      //--- Leave, if the element is blocked
      if(!m_slider_state)
         return;
      //--- Check and store the state of the mouse button
      CheckMouseOnLeftThumb();
      CheckMouseOnRightThumb();
      //--- Change the color of the slider runner
      ChangeThumbColor();
      //--- If the management is passed to the slider line (left slider runner)
      if(m_clamping_mouse_left_thumb==THUMB_PRESSED_INSIDE)
        {
         //--- Moving the slider runner
         OnDragLeftThumb(x);
         //--- Calculation of the slider runner position in the value range
         CalculateLeftThumbPos();
         //--- Setting a new value in the entry field
         ChangeLeftValue(m_left_current_pos);
         //--- Update the slider indicator
         UpdateIndicator();
         return;
        }
      //--- If the management is passed to the scrollbar (right slider runner)
      if(m_clamping_mouse_right_thumb==THUMB_PRESSED_INSIDE)
        {
         //--- Moving the slider runner
         OnDragRightThumb(x);
         //--- Calculation of the slider runner position in the value range
         CalculateRightThumbPos();
         //--- Setting a new value in the entry field
         ChangeRightValue(m_right_current_pos);
         //--- Update the slider indicator
         UpdateIndicator();
         return;
        }
     }
  }

You can download the files attached to this article and study the code of the CDualSlider class in detail. 

 


Testing the Dual Slider Control

Add the dual slider control to the graphical interface of the test application. Declare an instance of the CDualSlider class and a method with margins from the edge of the form in the CProgram custom class of the application:

//+------------------------------------------------------------------+
//| Class for creating the application                               |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- Sliders
   CDualSlider       m_dual_slider1;
   //---
private:
   //--- Sliders
#define DUALSLIDER1_GAP_X     (32)
#define DUALSLIDER1_GAP_Y     (275)
   bool              CreateDualSlider1(const string text);
  };

The code below shows the code of the CProgram::CreateDualSlider1() method. Set the value range from -2000 to 1000. The availability of this control will depend on the current state of the fifth checkbox similar to the simple slider that was created earlier in this article.

//+------------------------------------------------------------------+
//| Creates dual slider 1                                            |
//+------------------------------------------------------------------+
bool CProgram::CreateDualSlider1(const string text)
  {
//--- Store the window pointer
   m_dual_slider1.WindowPointer(m_window1);
//--- Coordinates
   int x=m_window1.X()+DUALSLIDER1_GAP_X;
   int y=m_window1.Y()+DUALSLIDER1_GAP_Y;
//--- Values
   double v1=(m_dual_slider1.GetLeftValue()==WRONG_VALUE) ? 0 : m_dual_slider1.GetLeftValue();
   double v2=(m_dual_slider1.GetRightValue()==WRONG_VALUE) ? 500 : m_dual_slider1.GetRightValue();
//--- Set properties before creation
   m_dual_slider1.XSize(264);
   m_dual_slider1.YSize(40);
   m_dual_slider1.EditXSize(87);
   m_dual_slider1.MaxValue(1000);
   m_dual_slider1.StepValue(1);
   m_dual_slider1.MinValue(-2000);
   m_dual_slider1.SetDigits(0);
   m_dual_slider1.SetLeftValue(v1);
   m_dual_slider1.SetRightValue(v2);
   m_dual_slider1.AreaColor(clrWhiteSmoke);
   m_dual_slider1.LabelColor(clrBlack);
   m_dual_slider1.LabelColorLocked(clrSilver);
   m_dual_slider1.EditColorLocked(clrWhiteSmoke);
   m_dual_slider1.EditBorderColor(clrSilver);
   m_dual_slider1.EditBorderColorLocked(clrSilver);
   m_dual_slider1.EditTextColorLocked(clrSilver);
   m_dual_slider1.SlotLineDarkColor(clrSilver);
   m_dual_slider1.SlotLineLightColor(clrWhite);
   m_dual_slider1.SlotYSize(4);
   m_dual_slider1.ThumbColorLocked(clrLightGray);
   m_dual_slider1.ThumbColorPressed(clrSilver);
   m_dual_slider1.SlotIndicatorColor(C'85,170,255');
   m_dual_slider1.SlotIndicatorColorLocked(clrLightGray);
//--- Create control
   if(!m_dual_slider1.CreateSlider(m_chart_id,m_subwin,text,x,y))
      return(false);
//--- Availability will depend on the current state of the fifth checkbox
   m_dual_slider1.SliderState(m_checkbox5.CheckButtonState());
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(0,m_dual_slider1);
   return(true);
  }

Do not forget to place the call of the method of the new control in the main method for creating the graphical interface as it is demonstrated in the shortened version of the code below. 

//+------------------------------------------------------------------+
//| Creates the trading panel                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Creating form 1 for controls
//--- Creating controls:
//    Main menu
//--- Context menus
//--- Creating the status bar
//--- Checkboxes
//--- Sliders
   if(!CreateDualSlider1("Dual Slider 1:"))
      return(false);
//--- Redrawing the chart
   m_chart.Redraw();
   return(true);
  }

Now, current state of the fifth checkbox will define two controls - the simple slider and the dual slider.

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- The text label press event
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LABEL)
     {
      ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
      //--- If the fifth checkbox was clicked on
      if(lparam==m_checkbox5.Id())
        {
         //--- Set state for sliders
         m_slider1.SliderState(m_checkbox5.CheckButtonState());
         m_dual_slider1.SliderState(m_checkbox5.CheckButtonState());
        }
     }
//--- The end of entering the value in the entry field event
   if(id==CHARTEVENT_CUSTOM+ON_END_EDIT)
     {
      ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
     }
  }

Now, compile the program and load it on to the chart. The screenshot below illustrates the result:

 Fig. 4. The dual slider control.

Fig. 4. The dual slider control.

 


Conclusion

In the sixth part of the series we considered six controls:

  • Checkbox.
  • The edit control.
  • Edit with checkbox.
  • Check combobox.
  • Slider.
  • Dual slider.

Currently, the schematic of the library for creating graphical interfaces looks as shown below:

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

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


In the next part of the series we will enrich our library with tables and tabs.

You can download the material of Part VI and test how it works. 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 sixth part:


Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/2468

Attached files |
Last comments | Go to discussion (1)
279DE7B1
279DE7B1 | 4 Mar 2021 at 07:53

Dear Anatoli Kazharski

Thanks for your sharing, it is very advanced. 

I am tring to compile in MT4 MetaEditor,the following errors occured. I don't know what is wrong for me, i am a beginner.


Looking forwards to your answer. complile errors

Universal Expert Advisor: A Custom Trailing Stop (Part 6) Universal Expert Advisor: A Custom Trailing Stop (Part 6)
The sixth part of the article about the universal Expert Advisor describes the use of the trailing stop feature. The article will guide you through how to create a custom trailing stop module using unified rules, as well as how to add it to the trading engine so that it would automatically manage positions.
Self-optimization of EA: Evolutionary and genetic algorithms Self-optimization of EA: Evolutionary and genetic algorithms
This article covers the main principles set fourth in evolutionary algorithms, their variety and features. We will conduct an experiment with a simple Expert Advisor used as an example to show how our trading system benefits from optimization. We will consider software programs that implement genetic, evolutionary and other types of optimization, and provide examples of application when optimizing a predictor set and parameters of the trading system.
Creating an assistant in manual trading Creating an assistant in manual trading
The number of trading robots used on the currency markets has significantly increased recently. They employ various concepts and strategies, however, none of them has yet succeeded to create a win-win sample of artificial intelligence. Therefore, many traders remain committed to manual trading. But even for such specialists, robotic assistants or, so called, trading panels, are created. This article is yet another example of creating a trading panel from scratch.
Graphical Interfaces VI: the Checkbox Control, the Edit Control and their Mixed Types (Chapter 1) Graphical Interfaces VI: the Checkbox Control, the Edit Control and their Mixed Types (Chapter 1)
This article is the beginning of the sixth part of the series dedicated to the development of the library for creating graphical interfaces in the MetaTrader terminals. In the first chapter, we are going to discuss the checkbox control, the edit control and their mixed types.