Русский 中文 Español Deutsch 日本語 Português
Graphical Interfaces XI: Rendered controls (build 14.2)

Graphical Interfaces XI: Rendered controls (build 14.2)

MetaTrader 5Examples | 21 August 2017, 10:47
8 059 14
Anatoli Kazharski
Anatoli Kazharski

Contents


Introduction

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

In the updated version of the library, all its controls will be drawn on separate graphical objects of the OBJ_BITMAP_LABEL type. In addition, we will continue to describe the global optimization of the library code. This description has been started in the previous article. Now let us consider the changes in the core classes of the library. The new version of the library has become even more object-oriented. The code has become even more comprehensible. This helps users to develop the library independently according to their own tasks. 


Methods for drawing controls

An instance of the canvas class has been declared in the CElement class. Its methods allow creating an object for drawing and deleting it. If necessary, its pointer can be obtained.

class CElement : public CElementBase
  {
protected:
   //--- Canvas for drawing a control
   CRectCanvas       m_canvas;
   //---
public:
   //--- Returns the pointer to the control's canvas
   CRectCanvas      *CanvasPointer(void) { return(::GetPointer(m_canvas)); }
  };

Now there is a general method for creating an object (canvas) for drawing the appearance of a control. It is located in the CElement base class and can be accessed from all classes of the library controls. The CElement::CreateCanvas() method is used for creating a graphical object of this type. As arguments, it must be passed the (1) name, (2) coordinates, (3) dimensions and (4) color format. The default format is COLOR_FORMAT_ARGB_NORMALIZE, which allows making the controls transparent. In case invalid dimensions are passed, they will be corrected in the beginning of the method. Once the object is created and attached to a chart with the MQL application, the base properties are set, which were previously repeated in all classes of controls.

class CElement : public CElementBase
  {
public:
   //--- Creating a canvas
   bool              CreateCanvas(const string name,const int x,const int y,
                                  const int x_size,const int y_size,ENUM_COLOR_FORMAT clr_format=COLOR_FORMAT_ARGB_NORMALIZE);
  };
//+------------------------------------------------------------------+
//| Creating a canvas for drawing a control                          |
//+------------------------------------------------------------------+
bool CElement::CreateCanvas(const string name,const int x,const int y,
                            const int x_size,const int y_size,ENUM_COLOR_FORMAT clr_format=COLOR_FORMAT_ARGB_NORMALIZE)
  {
//--- Adjust the sizes
   int xsize =(x_size<1)? 50 : x_size;
   int ysize =(y_size<1)? 20 : y_size;
//--- Reset the last error
   ::ResetLastError();
//--- Creating an object
   if(!m_canvas.CreateBitmapLabel(m_chart_id,m_subwin,name,x,y,xsize,ysize,clr_format))
     {
      ::Print(__FUNCTION__," > Failed to create a canvas for drawing the control ("+m_class_name+"): ",::GetLastError());
      return(false);
     }
//--- Reset the last error
   ::ResetLastError();
//--- Get the pointer to the base class
   CChartObject *chart=::GetPointer(m_canvas);
//--- Attach to the chart
   if(!chart.Attach(m_chart_id,name,(int)m_subwin,(int)1))
     {
      ::Print(__FUNCTION__," > Failed to attach the canvas for drawing to the chart: ",::GetLastError());
      return(false);
     }
//--- Properties
   m_canvas.Tooltip("\n");
   m_canvas.Corner(m_corner);
   m_canvas.Selectable(false);
//--- All controls, except the form, have a higher priority than the main control
   Z_Order((dynamic_cast<CWindow*>(&this)!=NULL)? 0 : m_main.Z_Order()+1);
//--- Coordinates
   m_canvas.X(x);
   m_canvas.Y(y);
//--- Size
   m_canvas.XSize(x_size);
   m_canvas.YSize(y_size);
//--- Offsets from the extreme point
   m_canvas.XGap(CalculateXGap(x));
   m_canvas.YGap(CalculateYGap(y));
   return(true);
  }

Let us move on to the base methods for drawing the controls. They are all located in the CElement class and declared as virtual

First of all comes drawing the background. In the basic version, it is a simple color fill, which uses the CElement::DrawBackground() method. If necessary, transparency can be enabled. To do this, use the CElement::Alpha() method, with the alpha channel value from 0 to 255 passed as the argument. A zero value means full transparency. In the current version, transparency applies only to the background fill and the border. Text and images will remain fully opaque and clear at any value of the alpha channel.

class CElement : public CElementBase
  {
protected:
   //--- Value of the alpha channel (transparency of the control)
   uchar             m_alpha;
   //---
public:
   //--- Value of the alpha channel (transparency of the control)
   void              Alpha(const uchar value)                        { m_alpha=value;                   }
   uchar             Alpha(void)                               const { return(m_alpha);                 }
   //---
protected:
   //--- Draws the background
   virtual void      DrawBackground(void);
  };
//+------------------------------------------------------------------+
//| Draws the background                                             |
//+------------------------------------------------------------------+
void CElement::DrawBackground(void)
  {
   m_canvas.Erase(::ColorToARGB(m_back_color,m_alpha));
  }

It is often necessary to draw a border for a particular control. The CElement::DrawBorder() method draws a border around the edges of the canvas object. The Rectangle() method can also be used for this purpose. It draws a rectangle without fill.

class CElement : public CElementBase
  {
protected:
   //--- Draws the frame
   virtual void      DrawBorder(void);
  };
//+------------------------------------------------------------------+
//| Draws the border                                                 |
//+------------------------------------------------------------------+
void CElement::DrawBorder(void)
  {
//--- Coordinates
   int x1=0,y1=0;
   int x2=m_canvas.X_Size()-1;
   int y2=m_canvas.Y_Size()-1;
//--- Draw a rectangle without fill
   m_canvas.Rectangle(x1,y1,x2,y2,::ColorToARGB(m_border_color,m_alpha));
  }

The previous article has already mentioned that any number of image groups can be assigned to any control. Therefore, the method for drawing a control must be able to output all images set by user. The CElement::DrawImage() method is used for this purpose here. The program sequentially iterates over all groups and images in them, outputting them to the canvas pixel by pixel. Before the cycle for outputting the image starts, the currently selected image in the group is determined. See the code of this method:

class CElement : public CElementBase
  {
protected:
   //--- Draws the image
   virtual void      DrawImage(void);
  };
//+------------------------------------------------------------------+
//| Draws the image                                                  |
//+------------------------------------------------------------------+
void CElement::DrawImage(void)
  {
//--- The number of groups
   uint group_total=ImagesGroupTotal();
//--- Draw the image
   for(uint g=0; g<group_total; g++)
     {
      //--- Index of the selected image
      int i=SelectedImage(g);
      //--- If there are no images
      if(i==WRONG_VALUE)
         continue;
      //--- Coordinates
      int x =m_images_group[g].m_x_gap;
      int y =m_images_group[g].m_y_gap;
      //--- Size
      uint height =m_images_group[g].m_image[i].Height();
      uint width  =m_images_group[g].m_image[i].Width();
      //--- Draw
      for(uint ly=0,p=0; ly<height; ly++)
        {
         for(uint lx=0; lx<width; lx++,p++)
           {
            //--- If there is no color, go to the next pixel
            if(m_images_group[g].m_image[i].Data(p)<1)
               continue;
            //--- Get the color of the lower layer (cell background) and color of the specified pixel of the icon
            uint background  =::ColorToARGB(m_canvas.PixelGet(x+lx,y+ly));
            uint pixel_color =m_images_group[g].m_image[i].Data(p);
            //--- Blend the colors
            uint foreground=::ColorToARGB(m_clr.BlendColors(background,pixel_color));
            //--- Draw the pixel of the overlay icon
            m_canvas.PixelSet(x+lx,y+ly,foreground);
           }
        }
     }
  }

Many controls have a text description. It can be displayed using the CElement::DrawText() method. Several fields in this method allow customizing the display of text depending on the state of the control. Three states of the control are available:

  • locked;
  • pressed;
  • focused (hovered by mouse).

In addition, the method considers if the mode of text alignment to the center is enabled. Here is its code:

class CElement : public CElementBase
  {
protected:
   //--- Draw text
   virtual void      DrawText(void);
  };
//+------------------------------------------------------------------+
//| Draws the text                                                   |
//+------------------------------------------------------------------+
void CElement::DrawText(void)
  {
//--- Coordinates
   int x =m_label_x_gap;
   int y =m_label_y_gap;
//--- Define the color for the text label
   color clr=clrBlack;
//--- If the control is locked
   if(m_is_locked)
      clr=m_label_color_locked;
   else
     {
      //--- If the control is pressed
      if(!m_is_pressed)
         clr=(m_mouse_focus)? m_label_color_hover : m_label_color;
      else
        {
         if(m_class_name=="CButton")
            clr=m_label_color_pressed;
         else
            clr=(m_mouse_focus)? m_label_color_hover : m_label_color_pressed;
        }
     }
//--- Font properties
   m_canvas.FontSet(m_font,-m_font_size*10,FW_NORMAL);
//--- Draw the text with consideration of the center alignment mode
   if(m_is_center_text)
     {
      x =m_x_size>>1;
      y =m_y_size>>1;
      m_canvas.TextOut(x,y,m_label_text,::ColorToARGB(clr),TA_CENTER|TA_VCENTER);
     }
   else
      m_canvas.TextOut(x,y,m_label_text,::ColorToARGB(clr),TA_LEFT);
  }

All of the above methods will be called in the common public virtual method CElement::Draw(). It does not have a base code, because the called set of methods for drawing will be unique in each control.

class CElement : public CElementBase
  {
public:
   //--- Draws the control
   virtual void      Draw(void) {}
  };

Consider the CElement::Update() method. It is called every time a program change is made to a control of the graphical interface. Two calling options are available: (1) completely redrawing the control or (2) applying the changes made before (see the code listing below). This method is also declared as virtual, since certain classes of controls may have their unique versions, which consider the peculiarities in the methods and sequence of rendering.

class CElement : public CElementBase
  {
public:
   //--- Updates the control to display the latest changes
   virtual void      Update(const bool redraw=false);
  };
//+------------------------------------------------------------------+
//| Updating the control                                             |
//+------------------------------------------------------------------+
void CElement::Update(const bool redraw=false)
  {
//--- With redrawing the control
   if(redraw)
     {
      Draw();
      m_canvas.Update();
      return;
     }
//--- Apply
   m_canvas.Update();
  }

New design of the graphical interface

Since all controls of the library are now rendered, it became possible to implement a new design for the graphical interface. There is no need to invent anything special, a ready-made solution can be used. The laconic aesthetics of the Windows 10 OS was used as the basis. 

Images for icons in such elements as form buttons for controls, radio buttons, checkboxes, comboboxes, menu items, tree list items and others have been made similar to Windows 10

It was mentioned earlier that transparency can now be set to any control. The screenshot below shows an example of a translucent window (CWindow). The value of the alpha channel here is 200.  

Fig. 8. Demonstration of the transparency of the form for controls. 

Fig. 8. Demonstration of the transparency of the form for controls.


To make the entire area of the form transparent, use the CWindow::TransparentOnlyCaption() method. The default mode applies transparency to header only

class CWindow : public CElement
  {
private:
   //--- Enables transparency only for the header
   bool              m_transparent_only_caption;
   //---
public:
   //--- Enables transparency mode only for the header
   void              TransparentOnlyCaption(const bool state) { m_transparent_only_caption=state; }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CWindow::CWindow(void) : m_transparent_only_caption(true)
  {
...
  }

Below is the appearance of the different types of buttons:

 Fig. 9. Demonstration of the appearance of several button types.

Fig. 9. Demonstration of the appearance of several button types.


The next screenshot shows the current appearance of the checkboxes, spin edit boxes, combobox with a drop-down list and a scrollbar, as well as the numeric sliders. Please note that it is now possible to make animated icons. The third item of the status bar imitates a disconnection from the server. Its appearance is an exact copy of a similar element in the status bar of MetaTrader 5.

Fig. 10. Demonstration of the appearance of checkboxes, combo boxes, sliders and other controls.

Fig. 10. Demonstration of the appearance of checkboxes, combo boxes, sliders and other controls.

The appearance of other controls from the graphical interface library can be seen in the test MQL application attached to this article.


Tooltips

Additional methods for managing the display of the tooltips in controls have been added to the CElement class. It is now possible to set a standard tooltip for any control, if its text does not exceed 63 characters. Use the CElement::Tooltip() methods for setting and getting the text of the tooltip.

class CElement : public CElementBase
  {
protected:
   //--- Tooltip text
   string            m_tooltip_text;
   //---
public:
   //--- Tooltip
   void              Tooltip(const string text)                      { m_tooltip_text=text;             }
   string            Tooltip(void)                             const { return(m_tooltip_text);          }
  };

Use the CElement::ShowTooltip() method to enable or disable the tooltip display. 

class CElement : public CElementBase
  {
public:
   //--- Tooltip display mode
   void              ShowTooltip(const bool state);
  };
//+------------------------------------------------------------------+
//| Setting the tooltip display                                      |
//+------------------------------------------------------------------+
void CElement::ShowTooltip(const bool state)
  {
   if(state)
      m_canvas.Tooltip(m_tooltip_text);
   else
      m_canvas.Tooltip("\n");
  }

Each class of controls has methods for getting the pointers to the nested controls. For example, if it is necessary to create tooltips for the form buttons, then the following lines of code should be added to the form creation method in the custom class:

...
//--- Set the tooltips
   m_window.GetCloseButtonPointer().Tooltip("Close");
   m_window.GetCollapseButtonPointer().Tooltip("Collapse/Expand");
   m_window.GetTooltipButtonPointer().Tooltip("Tooltips");
...

See how it works in the figure below. The standard tooltips have been enabled for the form buttons. In controls that might need a description longer than 63 characters, use the CTooltip control.

 Fig. 11. Demonstration of two types of tooltips (standard and custom).

Fig. 11. Demonstration of two types of tooltips (standard and custom).



New event identifiers

New event identifiers have been added. This significantly reduced the consumption of CPU resources. How has this been achieved?

When creating a large MQL application with a graphical interface and a lot of controls, it is important to minimize the CPU consumption.

If you the mouse over a control, it is highlighted. This indicates that the control is available for interaction. However, not all controls are available and visible at the same time.

  • Drop-down lists and calendars, context menus are hidden most of the time. They are opened occasionally, only to let the user select the necessary option, date or mode.
  • Groups of controls can be assigned to different tabs, but only one tab can be opened at a time. 
  • If the form is minimized, then all its controls are hidden as well.
  • If a dialog box is open, then only this form will have event handling.

Logically, there is no point in constantly processing the entire list of controls in the graphical interface when only some of them are available for use. It is necessary to generate an array of event handling only for the list of opened controls.

There are also controls with interactions that only affect the controls themselves. Therefore, such a control is the only one that must be left available for processing. Let us list these controls and cases:

  • Moving the scrollbar thumb (CScroll). It is necessary to enable processing only the scrollbar itself and the control it is part of (list view, table, multiline text box, etc.). 
  • Moving the slider thumb (CSlider). For this, it is sufficient for the slider control and spin edit box to be available, where the changes in values will be reflected.
  • Changing the width of columns in the table (CTable). Only the table must be left available for processing.
  • Changing the width of lists in the Tree view control (CTreeView). Only this control must be handled when dragging the adjacent border of lists.
  • Moving the form (CWindow). All controls are excluded from handling, except the form while it is moved.

In all listed cases, the controls need to send messages, that must be received and processed in the library core. The core will process two event identifiers for determining the availability of controls (ON_SET_AVAILABLE) and generating an array of controls (ON_CHANGE_GUI). All event identifiers are located in the Define.mqh file:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
...
#define ON_CHANGE_GUI               (28) // Graphical interface has changed
#define ON_SET_AVAILABLE            (39) // Set available items
...

Hide and show the controls using the methods Show() and Hide(). A new property has been added to the CElement class for setting the availability. Its value is set using the virtual public method CElement::IsAvailable(). Here, similar to other methods that define the state of controls, the passed value is set to nested controls as well. The priorities of the left mouse button click are set relative to the passed state. If the control must be unavailable, the priorities will be reset.

class CElement : public CElementBase
  {
protected:
   bool              m_is_available;   // availability
   //---
public:
   //--- Sign of available control
   virtual void      IsAvailable(const bool state)                   { m_is_available=state;                 }
   bool              IsAvailable(void)                         const { return(m_is_available);               }
  };
//+------------------------------------------------------------------+
//| Control availability                                             |
//+------------------------------------------------------------------+
void CElement::IsAvailable(const bool state)
  {
//--- Leave, if already set
   if(state==CElementBase::IsAvailable())
      return;
//--- Set
   CElementBase::IsAvailable(state);
//--- Other controls
   int elements_total=ElementsTotal();
   for(int i=0; i<elements_total; i++)
      m_elements[i].IsAvailable(state);
//--- Set priorities of the left mouse button click
   if(state)
      SetZorders();
   else
      ResetZorders();
  }

As an example, here is the code of the CComboBox::ChangeComboBoxListState() method, which determines the visibility of the drop-down list in the combobox control. 

If a combobox button is pressed and it is necessary to show the list view, then an event with the ON_SET_AVAILABLE identifier is sent immediately after the list view is displayed. As additional parameters, the (1) identifier of the control and (2) the flag of the required action for the event handler are passed: restore all visible controls or make available only the control that has the identifier specified in the event. A flag with the value 1 is used for restoring, while the value 0 sets the availability of the specified control. 

The message with the ON_SET_AVAILABLE identifier is followed by a message with the ON_CHANGE_GUI event identifier. Handling it will involve generation of an array with the currently available controls.

//+------------------------------------------------------------------+
//| Changes the current state of the combobox for the opposite       |
//+------------------------------------------------------------------+
void CComboBox::ChangeComboBoxListState(void)
  {
//--- If the button is pressed
   if(m_button.IsPressed())
     {
      //--- Show the list view
      m_listview.Show();
      //--- Send a message to determine the available controls
      ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),0,"");
      //--- Send a message about the change in the graphical interface
      ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,"");
     }
   else
     {
      //--- Hide the list view
      m_listview.Hide();
      //--- Send a message to restore the controls
      ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),1,"");
      //--- Send a message about the change in the graphical interface
      ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,"");
     }
  }

But for the Tabs control, for instance, it is sufficient to send only one of the described events for processing, the one with the ON_CHANGE_GUI identifier. There is no need to make certain controls available. When switching tabs, the visibility state is set to controls, that are assigned to the tab group. In the CTabs class, the visibility of controls groups is managed by the CTabs::ShowTabElements() method, which has been modified in the new version of the library. It may sometimes be necessary to place a group of tabs inside a tab. Therefore, even if displaying the controls of the selected tab reveals that one of them has a type of CTabs, then the CTabs::ShowTabElements() method will immediately be called in this control as well. This approach allows for placement of tabs at any level of nesting.

//+------------------------------------------------------------------+
//| Show controls of the selected tab only                           |
//+------------------------------------------------------------------+
void CTabs::ShowTabElements(void)
  {
//--- Leave, if the tabs are hidden
   if(!CElementBase::IsVisible())
      return;
//--- Check index of the selected tab
   CheckTabIndex();
//---
   uint tabs_total=TabsTotal();
   for(uint i=0; i<tabs_total; i++)
     {
      //--- Get the number of controls attached to the tab
      int tab_elements_total=::ArraySize(m_tab[i].elements);
      //--- If this tab is selected
      if(i==m_selected_tab)
        {
         //--- Display the tab controls
         for(int j=0; j<tab_elements_total; j++)
           {
            //--- Display the controls
            CElement *el=m_tab[i].elements[j];
            el.Reset();
            //--- If this is the Tabs control, show the controls of the opened one
            CTabs *tb=dynamic_cast<CTabs*>(el);
            if(tb!=NULL)
               tb.ShowTabElements();
           }
        }
      //--- Hide the controls of inactive tabs
      else
        {
         for(int j=0; j<tab_elements_total; j++)
            m_tab[i].elements[j].Hide();
        }
     }
//--- Send a message about it
   ::EventChartCustom(m_chart_id,ON_CLICK_TAB,CElementBase::Id(),m_selected_tab,"");
  }

Once the controls of the selected tab are displayed, the method sends a message stating that the graphical interface has changed and it is necessary to generate the array of controls available for processing.

//+------------------------------------------------------------------+
//| Pressing a tab in a group                                        |
//+------------------------------------------------------------------+
bool CTabs::OnClickTab(const int id,const int index)
  {
//--- Leave, if (1) the identifiers do not match or (2) the control is locked
   if(id!=CElementBase::Id() || CElementBase::IsLocked())
      return(false);
//--- Leave, if the index does not match
   if(index!=m_tabs.SelectedButtonIndex())
      return(true);
//--- Store index of the selected tab
   SelectedTab(index);
//--- Redraw the control
   Reset();
   Update(true);
//--- Show controls of the selected tab only
   ShowTabElements();
//--- Send a message about the change in the graphical interface
   ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0.0,"");
   return(true);
  }

Two new identifiers for generation of events have been added to the Defines.mqh file.

  • ON_MOUSE_FOCUS — mouse cursor entered the area of the control;
  • ON_MOUSE_BLUR — mouse cursor left the area of the control.

...
#define ON_MOUSE_BLUR               (34) // Mouse cursor left the area of the control
#define ON_MOUSE_FOCUS              (35) // Mouse cursor entered the area of the control
...

These events are generated only when the boundaries of controls are crossed. The base class of controls (CElementBase) contains the CElementBase::CheckCrossingBorder() method, which determines the moment the mouse cursor crosses the boundaries of the control areas. Let us supplement it with generation of the events described above:

//+------------------------------------------------------------------+
//| Checking the crossing of the control borders                     |
//+------------------------------------------------------------------+
bool CElementBase::CheckCrossingBorder(void)
  {
//--- If this is the moment of crossing the borders of the control
   if((MouseFocus() && !IsMouseFocus()) || (!MouseFocus() && IsMouseFocus()))
     {
      IsMouseFocus(MouseFocus());
      //--- Message about the crossing into the control
      if(MouseFocus())
         ::EventChartCustom(m_chart_id,ON_MOUSE_FOCUS,m_id,m_index,m_class_name);
      //--- Message about the crossing out of the control
      else
         ::EventChartCustom(m_chart_id,ON_MOUSE_BLUR,m_id,m_index,m_class_name);
      //---
      return(true);
     }
//---
   return(false);
  }

In the current version of the library, these events are handled only in the main menu (CMenuBar). Let us see how it works.

Once the main menu is created and stored, its items (CMenuItem) fall into the storage list as separate controls. The CMenuItem class is derived from CButton (the Button control). Therefore, a call to the event handler of the menu item starts with a call to the event handler of the CButton base class.

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CMenuItem::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handle the event in the base class
   CButton::OnEvent(id,lparam,dparam,sparam);
...
  }

The base event handler already contains tracking of the button crossing, it does not need to be duplicated in the CMenuItem derived class.

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CButton::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handling the mouse move event
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Redraw the control if borders were crossed
      if(CheckCrossingBorder())
         Update(true);
      //---
      return;
     }
...
  }

If the cursor crosses the border inside the button area, an event with the ON_MOUSE_FOCUS identifier is generated. Now the event handler of the CMenuBar class uses this very event to switch the context menus, when the Main menu control is activated. 

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CMenuBar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handling the event of changing focus on the menu items
   if(id==CHARTEVENT_CUSTOM+ON_MOUSE_FOCUS)
     {
      //--- Leave, if (2) the main menu has not been activated or (2) identifiers do not match
      if(!m_menubar_state || lparam!=CElementBase::Id())
         return;
      //--- Switch the context menu by the activated item of the main menu
      SwitchContextMenuByFocus();
      return;
     }
...
  }

Optimizing the library core

Let us consider the changes, fixes and additions made to the CWndContainer and CWndEvents classes, which can be called as the core of the library. After all, they organize the access to all of its controls and handle the event flows that are generated by the controls of the graphical interface.

A template method CWndContainer::ResizeArray() has been added to the CWndContainer class for working with arrays. An array of any type passed to this method will be increased by one element, and the method will return the index of the last element.

//+------------------------------------------------------------------+
//| Class for storing all interface objects                          |
//+------------------------------------------------------------------+
class CWndContainer
  {
private:
   //--- Increases the array by one element and returns the last index
   template<typename T>
   int               ResizeArray(T &array[]);
  };
//+------------------------------------------------------------------+
//| Increases the array by one element and returns the last index    |
//+------------------------------------------------------------------+
template<typename T>
int CWndContainer::ResizeArray(T &array[])
  {
   int size=::ArraySize(array);
   ::ArrayResize(array,size+1,RESERVE_SIZE_ARRAY);
   return(size);
  }

Let me remind you that private arrays have been declared in the WindowElements structure for many controls in the CWndContainer class (storage of pointers to all controls of the graphical interface). In order to obtain the number of controls of a certain type from this list, a universal CWndContainer::ElementsTotal() method has been implemented. Pass it the index of the window and the type of the control in order to obtain their number in the graphical interface of the MQL application. A new ENUM_ELEMENT_TYPE enumeration has been added to the Enums.mqh file for specifying the type of the control:

//+------------------------------------------------------------------+
//| Enumeration of the control types                                 |
//+------------------------------------------------------------------+
enum ENUM_ELEMENT_TYPE
  {
   E_CONTEXT_MENU    =0,
   E_COMBO_BOX       =1,
   E_SPLIT_BUTTON    =2,
   E_MENU_BAR        =3,
   E_MENU_ITEM       =4,
   E_DROP_LIST       =5,
   E_SCROLL          =6,
   E_TABLE           =7,
   E_TABS            =8,
   E_SLIDER          =9,
   E_CALENDAR        =10,
   E_DROP_CALENDAR   =11,
   E_SUB_CHART       =12,
   E_PICTURES_SLIDER =13,
   E_TIME_EDIT       =14,
   E_TEXT_BOX        =15,
   E_TREE_VIEW       =16,
   E_FILE_NAVIGATOR  =17,
   E_TOOLTIP         =18
  };

The code of the CWndContainer::ElementsTotal() method is presented in the following listing:

//+------------------------------------------------------------------+
//| No of controls of specified type at the specified window index   |
//+------------------------------------------------------------------+
int CWndContainer::ElementsTotal(const int window_index,const ENUM_ELEMENT_TYPE type)
  {
//--- Checking for exceeding the array range
   int index=CheckOutOfRange(window_index);
   if(index==WRONG_VALUE)
      return(WRONG_VALUE);
//---
   int elements_total=0;
//---
   switch(type)
     {
      case E_CONTEXT_MENU    : elements_total=::ArraySize(m_wnd[index].m_context_menus);   break;
      case E_COMBO_BOX       : elements_total=::ArraySize(m_wnd[index].m_combo_boxes);     break;
      case E_SPLIT_BUTTON    : elements_total=::ArraySize(m_wnd[index].m_split_buttons);   break;
      case E_MENU_BAR        : elements_total=::ArraySize(m_wnd[index].m_menu_bars);       break;
      case E_MENU_ITEM       : elements_total=::ArraySize(m_wnd[index].m_menu_items);      break;
      case E_DROP_LIST       : elements_total=::ArraySize(m_wnd[index].m_drop_lists);      break;
      case E_SCROLL          : elements_total=::ArraySize(m_wnd[index].m_scrolls);         break;
      case E_TABLE           : elements_total=::ArraySize(m_wnd[index].m_tables);          break;
      case E_TABS            : elements_total=::ArraySize(m_wnd[index].m_tabs);            break;
      case E_SLIDER          : elements_total=::ArraySize(m_wnd[index].m_sliders);         break;
      case E_CALENDAR        : elements_total=::ArraySize(m_wnd[index].m_calendars);       break;
      case E_DROP_CALENDAR   : elements_total=::ArraySize(m_wnd[index].m_drop_calendars);  break;
      case E_SUB_CHART       : elements_total=::ArraySize(m_wnd[index].m_sub_charts);      break;
      case E_PICTURES_SLIDER : elements_total=::ArraySize(m_wnd[index].m_pictures_slider); break;
      case E_TIME_EDIT       : elements_total=::ArraySize(m_wnd[index].m_time_edits);      break;
      case E_TEXT_BOX        : elements_total=::ArraySize(m_wnd[index].m_text_boxes);      break;
      case E_TREE_VIEW       : elements_total=::ArraySize(m_wnd[index].m_treeview_lists);  break;
      case E_FILE_NAVIGATOR  : elements_total=::ArraySize(m_wnd[index].m_file_navigators); break;
      case E_TOOLTIP         : elements_total=::ArraySize(m_wnd[index].m_tooltips);        break;
     }
//--- Return the number of controls of the specified type
   return(elements_total);
  }

To reduce the load on the CPU, it was necessary to add a few more arrays to the WindowElements structure, which will store the pointers to controls of the following categories.

  • Array of main controls
  • Array of controls with timers
  • Array of controls that are visible and available for processing
  • Array of controls with enabled auto-resizing along the X axis
  • Array of controls with enabled auto-resizing along the Y axis
class CWndContainer
  {
protected:
...
   //--- Structure of control arrays
   struct WindowElements
     {
      ...
      //--- Array of the main controls
      CElement         *m_main_elements[];
      //--- Timer controls
      CElement         *m_timer_elements[];
      //--- Controls that are currently visible and available
      CElement         *m_available_elements[];
      //--- Controls with auto-resizing along the X axis
      CElement         *m_auto_x_resize_elements[];
      //--- Controls with auto-resizing along the Y axis
      CElement         *m_auto_y_resize_elements[];
      ...
     };
   //--- Array of arrays of controls for each window
   WindowElements    m_wnd[];
...
  };

The sizes of these arrays are obtained by the appropriate methods:

class CWndContainer
  {
public:
   //--- The number of main controls
   int               MainElementsTotal(const int window_index);
   //--- The number of controls with timers
   int               TimerElementsTotal(const int window_index);
   //--- The number of controls with auto-resizing along the X axis
   int               AutoXResizeElementsTotal(const int window_index);
   //--- The number of controls with auto-resizing along the Y axis
   int               AutoYResizeElementsTotal(const int window_index);
   //--- The number of currently available controls
   int               AvailableElementsTotal(const int window_index);
  };

The CWndContainer::AddToElementsArray() method adds pointers to the array of main controls. The shortened version of the method:

//+------------------------------------------------------------------+
//| Adds pointer to the controls array                               |
//+------------------------------------------------------------------+
void CWndContainer::AddToElementsArray(const int window_index,CElementBase &object)
  {
...
//--- Add to the array of the main controls
   last_index=ResizeArray(m_wnd[window_index].m_main_elements);
   m_wnd[window_index].m_main_elements[last_index]=::GetPointer(object);
...
  }

Arrays of other categories are generated in the CWndEvents class (see below). Separate methods are used in them for adding pointers.

class CWndContainer
  {
protected:
   //--- Adds pointer to the array of controls with timers
   void              AddTimerElement(const int window_index,CElement &object);
   //--- Adds pointer to the array of controls with auto-resizing along the X axis
   void              AddAutoXResizeElement(const int window_index,CElement &object);
   //--- Adds pointer to the array of controls with auto-resizing along the Y axis
   void              AddAutoYResizeElement(const int window_index,CElement &object);
   //--- Adds pointer to the array of currently available controls
   void              AddAvailableElement(const int window_index,CElement &object);
  };
//+------------------------------------------------------------------+
//| Adds pointer to the array of controls with timers                |
//+------------------------------------------------------------------+
void CWndContainer::AddTimerElement(const int window_index,CElement &object)
  {
   int last_index=ResizeArray(m_wnd[window_index].m_timer_elements);
   m_wnd[window_index].m_timer_elements[last_index]=::GetPointer(object);
  }
//+------------------------------------------------------------------+
//| Adds pointer to the array of controls with auto-resizing (X)     |
//+------------------------------------------------------------------+
void CWndContainer::AddAutoXResizeElement(const int window_index,CElement &object)
  {
   int last_index=ResizeArray(m_wnd[window_index].m_auto_x_resize_elements);
   m_wnd[window_index].m_auto_x_resize_elements[last_index]=::GetPointer(object);
  }
//+------------------------------------------------------------------+
//| Adds pointer to the array of controls with auto-resizing (Y)     |
//+------------------------------------------------------------------+
void CWndContainer::AddAutoYResizeElement(const int window_index,CElement &object)
  {
   int last_index=ResizeArray(m_wnd[window_index].m_auto_y_resize_elements);
   m_wnd[window_index].m_auto_y_resize_elements[last_index]=::GetPointer(object);
  }
//+------------------------------------------------------------------+
//| Adds pointer to the array of available controls                  |
//+------------------------------------------------------------------+
void CWndContainer::AddAvailableElement(const int window_index,CElement &object)
  {
   int last_index=ResizeArray(m_wnd[window_index].m_available_elements);
   m_wnd[window_index].m_available_elements[last_index]=::GetPointer(object);
  }

There are also new methods for internal use in the CWndEvents class as well. Thus, the CWndEvents::Hide() method is required for hiding all controls of the graphical interface. It uses a double cycle: first the forms are hidden, and then the second cycle hides the controls attached to the form. Please note that in this method, the second cycle iterates over the array of controls, consisting of pointers to the main controls. The Hide() and Show() methods of controls are now arranged in such a way that they affect the entire chain of these methods in nested controls for the entire depth of the nesting.

//+------------------------------------------------------------------+
//| Class for event handling                                         |
//+------------------------------------------------------------------+
class CWndEvents : public CWndContainer
  {
protected:
   //--- Hide all controls
   void              Hide();
  };
//+------------------------------------------------------------------+
//| Hide the controls                                                |
//+------------------------------------------------------------------+
void CWndEvents::Hide(void)
  {
   int windows_total=CWndContainer::WindowsTotal();
   for(int w=0; w<windows_total; w++)
     {
      m_windows[w].Hide();
      int main_total=MainElementsTotal(w);
      for(int e=0; e<main_total; e++)
        {
         CElement *el=m_wnd[w].m_main_elements[e];
         el.Hide();
        }
     }
  }

This also has a new CWndEvents::Show() method for showing the controls in the specified form. The window specified in the argument is shown first. Then, if the window is not minimized, all controls attached to this form are made visible. This cycle skips only controls that are (1) drop-down or (2) the ones with a Tabs control designated as their main control. The controls in tabs are later shown using the CWndEvents::ShowTabElements() method outside the cycle.

class CWndEvents : public CWndContainer
  {
protected:
   //--- Show controls of the specified window
   void              Show(const uint window_index);
  };
//+------------------------------------------------------------------+
//| Show controls of the specified window                            |
//+------------------------------------------------------------------+
void CWndEvents::Show(const uint window_index)
  {
//--- Show controls of the specified window
   m_windows[window_index].Show();
//--- If the window is not minimized
   if(!m_windows[window_index].IsMinimized())
     {
      int main_total=MainElementsTotal(window_index);
      for(int e=0; e<main_total; e++)
        {
         CElement *el=m_wnd[window_index].m_main_elements[e];
         //--- Show the control, if it is (1) not drop-down and (2) its main control is not tab
         if(!el.IsDropdown() && dynamic_cast<CTabs*>(el.MainPointer())==NULL)
            el.Show();
        }
      //--- Show controls of the selected tabs only
      ShowTabElements(window_index);
     }
  }

The CWndEvents::Update() method will be required to redraw all controls of the graphical interface of the MQL application. This method can work in two modes: (1) fully redraw all controls or (2) apply the previously made changes. To fully redraw and update the graphical interface, it is necessary to pass the value true.

class CWndEvents : public CWndContainer
  {
protected:
   //--- Redraw the controls
   void              Update(const bool redraw=false);
  };
//+------------------------------------------------------------------+
//| Redraw the controls                                              |
//+------------------------------------------------------------------+
void CWndEvents::Update(const bool redraw=false)
  {
   int windows_total=CWndContainer::WindowsTotal();
   for(int w=0; w<windows_total; w++)
     {
      //--- Redraw the controls
      int elements_total=CWndContainer::ElementsTotal(w);
      for(int e=0; e<elements_total; e++)
        {
         CElement *el=m_wnd[w].m_elements[e];
         el.Update(redraw);
        }
     }
  }

We will discuss these methods a little later. For now, let us consider a number of methods designed for generating arrays for the aforementioned categories.

The previous version had a timer-based gradual change of controls' color when they were hovered by the mouse cursor. In order to reduce the volume and lower the consumption of resources, this superfluous feature has been removed. Therefore, the timer is not used in all controls of the current version of the library. It is present only in the fast scrolling of (1) the scrollbar thumbs, (2) the values in spin edit boxes and (3) the dates in the calendar. Therefore, only the appropriate controls are added to the corresponding array in the CWndEvents::FormTimerElementsArray() method (see the code listing below). 

Since the pointers to controls are stored in arrays of the base type of controls (CElement), the dynamic type casting (dynamic_cast) is used here and in many other methods of the classes for determining the derived type of controls. 

class CWndEvents : public CWndContainer
  {
protected:
   //--- Generates the array of controls with timers
   void              FormTimerElementsArray(void);
  };
//+------------------------------------------------------------------+
//| Generates the array of controls with timers                      |
//+------------------------------------------------------------------+
void CWndEvents::FormTimerElementsArray(void)
  {
   int windows_total=CWndContainer::WindowsTotal();
   for(int w=0; w<windows_total; w++)
     {
      int elements_total=CWndContainer::ElementsTotal(w);
      for(int e=0; e<elements_total; e++)
        {
         CElement *el=m_wnd[w].m_elements[e];
         //---
         if(dynamic_cast<CCalendar    *>(el)!=NULL ||
            dynamic_cast<CColorPicker *>(el)!=NULL ||
            dynamic_cast<CListView    *>(el)!=NULL ||
            dynamic_cast<CTable       *>(el)!=NULL ||
            dynamic_cast<CTextBox     *>(el)!=NULL ||
            dynamic_cast<CTextEdit    *>(el)!=NULL ||
            dynamic_cast<CTreeView    *>(el)!=NULL)
           {
            CWndContainer::AddTimerElement(w,el);
           }
        }
     }
  }

Now the timer has become much simpler: it no longer needs to check the entire list of controls, but only the ones that include this function:

//+------------------------------------------------------------------+
//| Checking events of all controls by timer                         |
//+------------------------------------------------------------------+
void CWndEvents::CheckElementsEventsTimer(void)
  {
   int awi=m_active_window_index;
   int timer_elements_total=CWndContainer::TimerElementsTotal(awi);
   for(int e=0; e<timer_elements_total; e++)
     {
      CElement *el=m_wnd[awi].m_timer_elements[e];
      if(el.IsVisible())
         el.OnEventTimer();
     }
  }

Handling the mouse over event is also required only for certain controls of the graphical interface. The array of controls available for handling such an event now excludes: 

  • CButtonsGroup — group of buttons;
  • CFileNavigator — file navigator;
  • CLineGraph — line chart;
  • CPicture — picture;
  • CPicturesSlider — picture slider;
  • CProgressBar — progress bar;
  • CSeparateLine — separation line;
  • CStatusBar — status bar;
  • CTabs — tabs;
  • CTextLabel — text label.

All these controls are not highlighted when hovered by mouse. However, some of them have nested controls that are highlighted. But since the common array is used in the cycle for forming the array of available controls, the nested controls will participate in the selection as well. The array will pick all controls that are visible, available and not locked

class CWndEvents : public CWndContainer
  {
protected:
   //--- Generates the array of available controls
   void              FormAvailableElementsArray(void);
  };
//+------------------------------------------------------------------+
//| Generates the array of available controls                        |
//+------------------------------------------------------------------+
void CWndEvents::FormAvailableElementsArray(void)
  {
//--- Window index
   int awi=m_active_window_index;
//--- The total number of controls
   int elements_total=CWndContainer::ElementsTotal(awi);
//--- Clear the array
   ::ArrayFree(m_wnd[awi].m_available_elements);
//---
   for(int e=0; e<elements_total; e++)
     {
      CElement *el=m_wnd[awi].m_elements[e];
      //--- Add only the controls that are visible and available for processing
      if(!el.IsVisible() || !el.IsAvailable() || el.IsLocked())
         continue;
      //--- Exclude the controls that do not require handling the mouseover events
      if(dynamic_cast<CButtonsGroup   *>(el)==NULL &&
         dynamic_cast<CFileNavigator  *>(el)==NULL &&
         dynamic_cast<CLineGraph      *>(el)==NULL &&
         dynamic_cast<CPicture        *>(el)==NULL &&
         dynamic_cast<CPicturesSlider *>(el)==NULL &&
         dynamic_cast<CProgressBar    *>(el)==NULL &&
         dynamic_cast<CSeparateLine   *>(el)==NULL &&
         dynamic_cast<CStatusBar      *>(el)==NULL &&
         dynamic_cast<CTabs           *>(el)==NULL &&
         dynamic_cast<CTextLabel      *>(el)==NULL)
        {
         AddAvailableElement(awi,el);
        }
     }
  }

It remains to consider the CWndEvents::FormAutoXResizeElementsArray() and CWndEvents::FormAutoYResizeElementsArray() methods, which generate arrays with pointers to controls that have the auto-resizing modes enabled. Such controls follow the sizes of the main controls they are attached to. Not all controls have a method code for auto-resizing. Here are the ones that have it:

Controls that have the code defined in the CElement::ChangeWidthByRightWindowSide() virtual method for auto-resizing the width:

  • CButton — button.
  • CFileNavigator — file navigator.
  • CLineGraph — line chart.
  • CListView — list view.
  • CMenuBar — main menu.
  • CProgressBar — progress bar.
  • CStandardChart — standard chart.
  • CStatusBar — status bar.
  • CTable — table.
  • CTabs — tabs.
  • CTextBox — text edit box.
  • CTextEdit — edit box.
  • CTreeView — tree view.

Controls that have the code defined in the CElement::ChangeHeightByBottomWindowSide() virtual method for auto-resizing the height:

  • CLineGraph — line chart.
  • CListView — list view.
  • CStandardChart — standard chart.
  • CTable — table.
  • CTabs — tabs.
  • CTextBox — text edit box.

When creating arrays for these categories, it is checked whether the auto-resizing modes are enabled in these controls; if enabled, the controls are added to the array. The code of these will not be considered: similar methods have been discussed above. 

Now let us find out when the arrays for the categories listed above are generated. In the main method for creating the graphical interface (that user creates on his own), once all specified controls are successfully created, it is now necessary to call only one method CWndEvents::CompletedGUI() to display them all on the chart. It signals that program that the creation of the graphical interface of the MQL application is completed. 

Let us consider the CWndEvents::CompletedGUI() method in details. It calls all the methods described earlier in this section. First, all controls of the graphical interface are hidden. None of them is rendered yet. Therefore, to avoid their gradual sequential appearance, they need to be hidden before rendering. Next, the rendering itself takes place, and the latest changes are applied to each control. After that, it is necessary to display only the controls of the main window. Then the arrays of pointers to controls by categories are generates. At the end of the method, the chart is updated. 

class CWndEvents : public CWndContainer
  {
protected:
   //--- Finishing the creation of GUI
   void              CompletedGUI(void);
  };
//+------------------------------------------------------------------+
//| Finishing the creation of GUI                                    |
//+------------------------------------------------------------------+
void CWndEvents::CompletedGUI(void)
  {
//--- Leave, if there is no window yet
   int windows_total=CWndContainer::WindowsTotal();
   if(windows_total<1)
      return;
//--- Show the comment informing the user
   ::Comment("Update. Please wait...");
//--- Hide the controls
   Hide();
//--- Draw the controls
   Update(true);
//--- Show the controls of the activated window
   Show(m_active_window_index);
//--- Generate the array of controls with timers
   FormTimerElementsArray();
//--- Generates the array of visible and at the same time available controls
   FormAvailableElementsArray();
//--- Generate the arrays of controls with auto-resizing
   FormAutoXResizeElementsArray();
   FormAutoYResizeElementsArray();
//--- Redraw the chart
   m_chart.Redraw();
//--- Clear the comment
   ::Comment("");
  }

The CWndEvents::CheckElementsEvents() method for checking and handling the events of controls has been significantly modified. Let us dwell on this in more detail. 

This method now has two blocks of event handling. One block is designed exclusively for handling the mouse cursor movement (CHARTEVENT_MOUSE_MOVE). Instead of looping through the list of all controls of the activated window, as it was before, the cycle now only iterates over controls available for processing. This is the reason why the array with the pointers to available controls was generated in the first place. The graphical interface of a large MQL application may have several hundreds and even thousands of controls, and only a few from the list can be visible and available at a time. This approach greatly saves the CPU resources. 

Another modification is that now the checks (1) of the subwindow the form is located in and (2) the focus over the control are now performed in an external cycle, and not in the handlers of each class of controls. Thus, the checks relevant to each control are now located in the same place. This will be convenient in the future, in case it is necessary to make changes to the event handling algorithm.

All other types of event are handled in a separate block. The current version iterates over the entire list of controls of the graphical interface. All checks that were previously located in the classes of controls have also been moved to an external cycle. 

At the very end of the method, the event is sent to the custom class of the MQL application.

//+------------------------------------------------------------------+
//| Checking control events                                          |
//+------------------------------------------------------------------+
void CWndEvents::CheckElementsEvents(void)
  {
//--- Handling the event of moving the mouse cursor
   if(m_id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Leave, if the form is in another subwindow of the chart
      if(!m_windows[m_active_window_index].CheckSubwindowNumber())
         return;
      //--- Check only the available controls
      int available_elements_total=CWndContainer::AvailableElementsTotal(m_active_window_index);
      for(int e=0; e<available_elements_total; e++)
        {
         CElement *el=m_wnd[m_active_window_index].m_available_elements[e];
         //--- Checking the focus over controls
         el.CheckMouseFocus();
         //--- Handling the event
         el.OnEvent(m_id,m_lparam,m_dparam,m_sparam);
        }
     }
//--- All events, except the mouse cursor movement
   else
     {
      int elements_total=CWndContainer::ElementsTotal(m_active_window_index);
      for(int e=0; e<elements_total; e++)
        {
         //--- Check only the available controls
         CElement *el=m_wnd[m_active_window_index].m_elements[e];
         if(!el.IsVisible() || !el.IsAvailable() || el.IsLocked())
            continue;
         //--- Handling the event in the control's event handler
         el.OnEvent(m_id,m_lparam,m_dparam,m_sparam);
        }
     }
//--- Forwarding the event to the application file
   OnEvent(m_id,m_lparam,m_dparam,m_sparam);
  }

The CWndEvents::FormAvailableElementsArray() method for generating the array of controls, that are visible and at same time available for processing, is called in the following cases:

  • Opening a dialog box. Once a dialog box is opened, the ON_OPEN_DIALOG_BOX event is generated, which is handled in the CWndEvents::OnOpenDialogBox() method. After handling this event, it is necessary to generate the array of available controls for the opened window.

  • Changes in the graphical interface. Any change in the graphical interface caused by interaction generates the ON_CHANGE_GUI event. It is handled by a new private method CWndEvents::OnChangeGUI(). Here, when the ON_CHANGE_GUI event arrives, the array of available controls is generated first. Then all tooltips are moved to the top layer. At the end of the method, the chart is redrawn to display the latest changes.

class CWndEvents : public CWndContainer
  {
private:
   //--- Changes in the graphical interface
   bool              OnChangeGUI(void);
  };
//+------------------------------------------------------------------+
//| Event of changes in the graphical interface                      |
//+------------------------------------------------------------------+
bool CWndEvents::OnChangeGUI(void)
  {
//--- If the signal is about the change in the graphical interface
   if(m_id!=CHARTEVENT_CUSTOM+ON_CHANGE_GUI)
      return(false);
//--- Generates the array of visible and at the same time available controls
   FormAvailableElementsArray();
//--- Move tooltips to the top layer
   ResetTooltips();
//--- Redraw the chart
   m_chart.Redraw();
   return(true);
  }

Next, consider the handling of an event with the ON_SET_AVAILABLE identifier for determining controls that will be available for processing. 

The CWndEvents::OnSetAvailable() method has been implemented for handling the ON_SET_AVAILABLE event. But before proceeding to the description of its code, it is necessary to consider a number of auxiliary methods. There are 10 graphical interface controls that generate events with such identifier. They all have the means to determine their activated state. Let us name them:

  • Main menu — CMenuBar::State().
  • Menu item — CMenuItem::GetContextMenuPointer().IsVisible().
  • Split button — CSplitButton::GetContextMenuPointer().IsVisible().
  • Combobox — CComboBox::GetListViewPointer().IsVisible().
  • Drop-down calendar — DropCalendar::GetCalendarPointer().IsVisible().
  • Scrollbar — CScroll::State().
  • Table — CTable::ColumnResizeControl().
  • Numerical slider — CSlider::State().
  • Tree view — CTreeView::GetMousePointer().State().
  • Standard chart — CStandartChart::GetMousePointer().IsVisible().

Each of these controls has private arrays in the CWndContainer class. The CWndEvents class implements methods for determining which of the controls is currently activated. All these methods return the index of the activated control in its private array.

class CWndEvents : public CWndContainer
  {
private:
   //--- Returns the index of the activated main menu
   int               ActivatedMenuBarIndex(void);
   //--- Returns the index of the activated menu item
   int               ActivatedMenuItemIndex(void);
   //--- Returns the index of the activated split button
   int               ActivatedSplitButtonIndex(void);
   //--- Returns the index of the activated combobox
   int               ActivatedComboBoxIndex(void);
   //--- Returns the index of the activated drop-down calendar
   int               ActivatedDropCalendarIndex(void);
   //--- Returns the index of the activated scrollbar
   int               ActivatedScrollIndex(void);
   //--- Returns the index of the activated table
   int               ActivatedTableIndex(void);
   //--- Returns the index of the activated slider
   int               ActivatedSliderIndex(void);
   //--- Returns the index of the activated tree view
   int               ActivatedTreeViewIndex(void);
   //--- Returns the index of the activated subchart
   int               ActivatedSubChartIndex(void);
  };

As the only different in most of these methods lies in the conditions for determining the states of the controls, only the code for one of them will be considered. The listing below shows the code of the CWndEvents::ActivatedTreeViewIndex() method, which returns the index of the activated tree view. If this type of control has the tab items mode enabled, the check is rejected.

//+------------------------------------------------------------------+
//| Returns the index of the activated tree view                     |
//+------------------------------------------------------------------+
int CWndEvents::ActivatedTreeViewIndex(void)
  {
   int index=WRONG_VALUE;
//---
   int total=ElementsTotal(m_active_window_index,E_TREE_VIEW);
   for(int i=0; i<total; i++)
     {
      CTreeView *el=m_wnd[m_active_window_index].m_treeview_lists[i];
      //--- Go to the next, if the tabs mode is enabled
      if(el.TabItemsMode())
         continue;
      //--- If in the process of changing the width of lists 
      if(el.GetMousePointer().State())
        {
         index=i;
         break;
        }
     }
   return(index);
  }

The CWndEvents::SetAvailable() method is designed for setting the availability states of controls. As arguments, it is necessary to pass (1) the required form index and (2) the state to set for the controls. 

If it is necessary to make all controls unavailable, simply iterate over them in a cycle and set the value false

If it is necessary to make the controls available, then the overloaded CTreeView::IsAvailable() method of the same name is called for the tree views, which contains two modes for setting the state: (1) only for the main control and (2) for all controls for the full depth of nesting. Therefore, the dynamic type casting is used here to get the pointer to control of the derived control class

class CWndEvents : public CWndContainer
  {
protected:
   //--- Sets the availability states of controls
   void              SetAvailable(const uint window_index,const bool state);
  };
//+------------------------------------------------------------------+
//| Sets the availability states of controls                         |
//+------------------------------------------------------------------+
void CWndEvents::SetAvailable(const uint window_index,const bool state)
  {
//--- Get the number of the main controls
   int main_total=MainElementsTotal(window_index);
//--- If it is necessary to make the controls unavailable
   if(!state)
     {
      m_windows[window_index].IsAvailable(state);
      for(int e=0; e<main_total; e++)
        {
         CElement *el=m_wnd[window_index].m_main_elements[e];
         el.IsAvailable(state);
        }
     }
   else
     {
      m_windows[window_index].IsAvailable(state);
      for(int e=0; e<main_total; e++)
        {
         CElement *el=m_wnd[window_index].m_main_elements[e];
         //--- If it is a tree view
         if(dynamic_cast<CTreeView*>(el)!=NULL)
           {
            CTreeView *tv=dynamic_cast<CTreeView*>(el);
            tv.IsAvailable(true);
            continue;
           }
         //--- If it is a file navigator
         if(dynamic_cast<CFileNavigator*>(el)!=NULL)
           {
            CFileNavigator *fn =dynamic_cast<CFileNavigator*>(el);
            CTreeView      *tv =fn.GetTreeViewPointer();
            fn.IsAvailable(state);
            tv.IsAvailable(state);
            continue;
           }
         //--- Make the control available
         el.IsAvailable(state);
        }
     }
  }

Menu items with context menus attached required a method, which would allow looping through the entire depth of opened context menus, accessing them. In this case, it will be necessary to make the context menus available for processing. This will be implemented recursively. 

Below is the code of the CWndEvents::CheckContextMenu() method. First, pass an object of the menu item type and attempt to get the pointer to the context menu. If the pointer is correct, check if this context menu is opened. If so, set the availability flag to it. Then set the availability status to all items of this menu in a cycle. At the same time, check each item if it has a context menu using the CWndEvents::CheckContextMenu() method.

class CWndEvents : public CWndContainer
  {
private:
   //--- Checks and makes the context menu available
   void              CheckContextMenu(CMenuItem &object);
  };
//+------------------------------------------------------------------+
//| Recursively check and make the context menus available           |
//+------------------------------------------------------------------+
void CWndEvents::CheckContextMenu(CMenuItem &object)
  {
//--- Getting the context menu pointer
   CContextMenu *cm=object.GetContextMenuPointer();
//--- Leave, if there is no context menu in the item
   if(::CheckPointer(cm)==POINTER_INVALID)
      return;
//--- Leave, if there is a context menu, but it is hidden
   if(!cm.IsVisible())
      return;
//--- Set the available control signs
   cm.IsAvailable(true);
//---
   int items_total=cm.ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Set the available control signs
      CMenuItem *mi=cm.GetItemPointer(i);
      mi.IsAvailable(true);
      //--- Check if this item has a context menu
      CheckContextMenu(mi);
     }
  }

Now let us consider the CWndEvents::OnSetAvailable() method, which handles the event for determining the available controls. 

If a custom event with the ON_SET_AVAILABLE identifier is received, it is first necessary to determine if there are currently activated controls. The local variables store the indexes of the activated controls for quick access to their private arrays.

If a signal to determine the available controls is received, then first disable the access in the entire list. If the signal is to restore, then after checking for absence of activated controls, the access is restored in the entire list and the program leaves the method.

If the program reaches the next code block in this method, this means that it is either (1) a signal to determine the available controls, or (2) to restore, but there is an activated drop-down calendar. The second situation may occur when opening a drop-down calendar with an activated combobox, where the drop-down list was closed.

If one of the described conditions is met, attempt to get the pointer to the activated control. If the pointer was not received, the program leaves the method.

If the pointer was obtained, the control is made available. For some controls, there are nuances of how this is done. Here are all such cases:

  • Main menu (CMenuBar). If it is activated, then it is necessary to make available all open context menus related to it. For this purpose, the recursive method CWndEvents::CheckContextMenu() has been considered in the code listing above.

  • Menu item (CMenuItem). Menu items can be independent controls, to which context menus can be attached. Therefore, if such a control is activated, the control itself (menu item) is also made available here, as well as all context menus opened from it.

  • Scrollbar (CScroll). If a scrollbar is activated (thumb in the process of moving), then make available all the controls, starting from the first. For example, if the scrollbar is attached to a list view, then the list view and all its controls will become available, for the full depth of nesting.

  • Tree view (CTreeView). This control can be activated when the width of its lists is being changed. It is necessary to exclude the processing of list views on mouseover, and make the tree view itself available for processing.

Below is the code of the CWndEvents::OnSetAvailable() method.

class CWndEvents : public CWndContainer
  {
private:
   //--- Determine the available controls
   bool              OnSetAvailable(void);
  };
//+------------------------------------------------------------------+
//| Event for determining the available controls                     |
//+------------------------------------------------------------------+
bool CWndEvents::OnSetAvailable(void)
  {
//--- If the signal is about changing the availability of controls
   if(m_id!=CHARTEVENT_CUSTOM+ON_SET_AVAILABLE)
      return(false);
//--- Signal to set/restore
   bool is_restore=(bool)m_dparam;
//--- Determine the active controls
   int mb_index =ActivatedMenuBarIndex();
   int mi_index =ActivatedMenuItemIndex();
   int sb_index =ActivatedSplitButtonIndex();
   int cb_index =ActivatedComboBoxIndex();
   int dc_index =ActivatedDropCalendarIndex();
   int sc_index =ActivatedScrollIndex();
   int tl_index =ActivatedTableIndex();
   int sd_index =ActivatedSliderIndex();
   int tv_index =ActivatedTreeViewIndex();
   int ch_index =ActivatedSubChartIndex();
//--- If the signal is to determine the available controls, disable access first 
   if(!is_restore)
      SetAvailable(m_active_window_index,false);
//--- Restore only if there are no activated items
   else
     {
      if(mb_index==WRONG_VALUE && mi_index==WRONG_VALUE && sb_index==WRONG_VALUE &&
         dc_index==WRONG_VALUE && cb_index==WRONG_VALUE && sc_index==WRONG_VALUE &&
         tl_index==WRONG_VALUE && sd_index==WRONG_VALUE && tv_index==WRONG_VALUE && ch_index==WRONG_VALUE)
        {
         SetAvailable(m_active_window_index,true);
         return(true);
        }
     }
//--- If (1) the signal is to disable access or (2) to restore the drop-down calendar
   if(!is_restore || (is_restore && dc_index!=WRONG_VALUE))
     {
      CElement *el=NULL;
      //--- Main menu
      if(mb_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_menu_bars[mb_index];      }
      //--- Menu item
      else if(mi_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_menu_items[mi_index];     }
      //--- Split button
      else if(sb_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_split_buttons[sb_index];  }
      //--- Drop-down calendar without a drop-down list
      else if(dc_index!=WRONG_VALUE && cb_index==WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_drop_calendars[dc_index]; }
      //--- Drop-down list
      else if(cb_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_combo_boxes[cb_index];    }
      //--- Scrollbar
      else if(sc_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_scrolls[sc_index];        }
      //--- Table
      else if(tl_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_tables[tl_index];         }
      //--- Slider
      else if(sd_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_sliders[sd_index];        }
      //--- Tree view
      else if(tv_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_treeview_lists[tv_index]; }
      //--- Subchart
      else if(ch_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_sub_charts[ch_index];     }
      //--- Leave, if the control pointer is not received
      if(::CheckPointer(el)==POINTER_INVALID)
         return(true);
      //--- Block for the main menu
      if(mb_index!=WRONG_VALUE)
        {
         //--- Make the main menu and its visible context menus available
         el.IsAvailable(true);
         //--- 
         CMenuBar *mb=dynamic_cast<CMenuBar*>(el);
         int items_total=mb.ItemsTotal();
         for(int i=0; i<items_total; i++)
           {
            CMenuItem *mi=mb.GetItemPointer(i);
            mi.IsAvailable(true);
            //--- Checks and makes the context menu available
            CheckContextMenu(mi);
           }
        }
      //--- Block for the menu item
      if(mi_index!=WRONG_VALUE)
        {
         CMenuItem *mi=dynamic_cast<CMenuItem*>(el);
         mi.IsAvailable(true);
         //--- Checks and makes the context menu available
         CheckContextMenu(mi);
        }
      //--- Block for the scrollbar
      else if(sc_index!=WRONG_VALUE)
        {
         //--- Make available starting from the main node
         el.MainPointer().IsAvailable(true);
        }
      //--- Block for the tree view
      else if(tv_index!=WRONG_VALUE)
        {
         //--- Lock all controls except the main control
         CTreeView *tv=dynamic_cast<CTreeView*>(el);
         tv.IsAvailable(true,true);
         int total=tv.ElementsTotal();
         for(int i=0; i<total; i++)
            tv.Element(i).IsAvailable(false);
        }
      else
        {
         //--- Make the control available
         el.IsAvailable(true);
        }
     }
//---
   return(true);
  }



Application for testing the controls

An MQL application has been implemented for testing purposes. Its graphical interface contains all the controls of the library. This how it is looks: 

Fig. 12. Graphical interface of the test MQL application.

Fig. 12. Graphical interface of the test MQL application.


You can download it at the end of the article for a more detailed study.



Conclusion

This version of the library has significant differences from the one presented in the article Graphical Interfaces X: Text selection in the Multiline Text box (build 13). A lot of work was done, which affected almost all files of the library. Now all controls of the library are drawn on separate objects. Code readability has improved, the code volume has decreased by approximately 30%, and its features have expanded. A number of other errors and flaws reported by users have been fixed.

If you have already started creating your MQL applications using the previous version of the library, it is recommended that you first download the new version to a separately installed copy of the MetaTrader 5 terminal in order to study and thoroughly test the library.

The library for creating graphical interfaces at the current stage of development looks like in the schematic below. This is not the final version of the library: it will evolve and improve in the future.

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

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



If you have questions on using the material presented in those files, you can refer to the detailed description of the library development in one of the articles of this series or ask your question in the comments of this article.

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

Attached files |
Last comments | Go to discussion (14)
Ex Ovo Omnia
Ex Ovo Omnia | 30 Aug 2017 at 10:40
Artur Zas:

Hi, The set of controls is really nice and helpful, but it would be even better if you can handle higher DPI setting in a better way.

Obviously, the bitmaps will display correctly, but the text does not. ...


I would say the text is scaled properly, unlike all the rest of the graphics.

Artur Zas
Artur Zas | 30 Aug 2017 at 10:56
Ex Ovo Omnia:

I would say the text is scaled properly, unlike all the rest of the graphics.


That depends how you look at it. You can either scale up the bitmaps to match the font size of scale down the font size to match the size of the bitmaps.

Scaling up bitmaps is not really something you want to do because unlike vector graphics bitmaps will loose quality when scaled up. The font on the other hand is vector based so can be scaled up and down without loss in quality.

Obviously the bast way to cope with the problem would be to use vector graphics, but I don't think the platform supports it (although I may be wrong here as I don't really do graphics in metatrader).

Personally I scale down the font proportionally to match the DPI. 

Anyway, it would be nice if the controls could handle this by default so we don't need to worry about it.

Ex Ovo Omnia
Ex Ovo Omnia | 30 Aug 2017 at 16:19
Artur Zas:

That depends how you look at it. You can either scale up the bitmaps to match the font size of scale down the font size to match the size of the bitmaps.

Scaling up bitmaps is not really something you want to do because unlike vector graphics bitmaps will loose quality when scaled up. The font on the other hand is vector based so can be scaled up and down without loss in quality.

Obviously the bast way to cope with the problem would be to use vector graphics, but I don't think the platform supports it (although I may be wrong here as I don't really do graphics in metatrader).

Personally I scale down the font proportionally to match the DPI. 

Anyway, it would be nice if the controls could handle this by default so we don't need to worry about it.


I do not contradict the picture scaling difficulties, I just can spot that the font size change corresponds to the scaled Metatrader application GUI. So if you decrease the font size, you could make the letters rather tiny on the hi-res monitor.

Artur Zas
Artur Zas | 30 Aug 2017 at 16:30
Ex Ovo Omnia:

I do not contradict the picture scaling difficulties, I just can spot that the font size change corresponds to the scaled Metatrader application GUI. So if you decrease the font size, you could make the letters rather tiny on the hi-res monitor.

Yes. I agree. It would be better to have the graphics match the size of the font which indeed matches the scaled up MT5 GUI. I was just looking for a way to have this better looking in a way that the font size matches the rest of the graphics.

I guess this is the best we are going to get in a mixed environment of raster & vector graphics.

Kristina Suh
Kristina Suh | 18 Jan 2021 at 02:13
Artur Zas:

Technically speaking it should as OBJ-BITMAP_LABEL is also available in MQL4, but you would need to adjust the library code as there are a few places where the code will just not compile using the MQL4 compiler.

I'll try to play around with the library and I'll let you know, if I'm able to somehow port it to MQL4. You can also try by yourself. Just rename the example EA's .mq5 extension to .mq4 and try to compile the code.

Obviously, you will get compilation errors however you can check where and what they are. 

Hi Artur,

I know this is an old post, I was wondering how you went with converting this to MT4.. Any luck?

thanks

Testing patterns that arise when trading currency pair baskets. Part I Testing patterns that arise when trading currency pair baskets. Part I
We begin testing the patterns and trying the methods described in the articles about trading currency pair baskets. Let's see how oversold/overbought level breakthrough patterns are applied in practice.
Angles in Trading. Further Study Required Angles in Trading. Further Study Required
In this article, we discuss the method of trading analysis by measuring angles in the MetaTrader 4 terminal. The article provides a general plan of using angles for trend movement analysis, as well as non-standard ways to the practical application of angle analysis in trading. The article also provides conclusions that can be useful for trading.
Universal Expert Advisor: Accessing Symbol Properties (Part 8) Universal Expert Advisor: Accessing Symbol Properties (Part 8)
The eighth part of the article features the description of the CSymbol class, which is a special object that provides access to any trading instrument. When used inside an Expert Advisor, the class provides a wide set of symbol properties, while allowing to simplify Expert Advisor programming and to expand its functionality.
Patterns available when trading currency baskets. Part III Patterns available when trading currency baskets. Part III
This is the final article devoted to the patterns that occur when trading currency pair baskets. It considers combined trend-following indicators and application of standard graphical constructions.