Русский 中文 Español Deutsch 日本語 Português
Graphical Interfaces II: the Menu Item Element (Chapter 1)

Graphical Interfaces II: the Menu Item Element (Chapter 1)

MetaTrader 5Examples | 2 March 2016, 15:51
12 126 1
Anatoli Kazharski
Anatoli Kazharski

Content


Introduction

In the chapters of the first series, we discussed in full the process of developing the main structure of the library for creating graphical interfaces. There, we also created the main element of the interface - a form for controls. The first article: Graphical Interfaces I: Preparation of the Library Structure (Chapter 1) explains in detail what this library is for. A complete list of links to the articles of the first part is at the end of each chapter. There, you can also download a complete version of the library at the current stage of development. The files must be placed in the same directories as they are located in the archive.

Some classes at the current stage of development are still incomplete. As the library is enriched with new controls, some additions will have to be introduced in the CWndContainer class, which is the base for storing pointers to elements and objects constituting them. Additions will also be required in the CWndEvents class. This is because the chart events and the events generated by controls will be handled there.

The current version of the CWindow form class for controls is also not final. The multi-window mode mentioned in the previous articles has not been implemented yet. We will realize this mode in the fourth part of this series. Added to that, the creation of a context menu will be demonstrated. Although this is a part of the main menu, it can be a part of some other controls which will be considered in other articles of this series.

It is recommended to carry out all actions consistently for better understanding of the material. The code of the methods belonging to the same type and repeated in different classes will be omitted for saving space in the article. If you come across such methods, refer to the files attached to this article to find the required code and then continue studying the material of the article.


The Main Menu of the Program

It is difficult to find a program that does not have the main menu. The MetaTrader terminals also have this interface element (see the screenshot below). Usually, the menu is located in the top left part of the program window and consists of several items. A left mouse click on a menu item brings up a drop-down list with the program options.

Fig. 1. Main menu in the MetaTrader 5 terminal

Fig. 1. Main menu in the MetaTrader 5 terminal

This drop-down list is called a context menu and can contain several types of items. Let us consider each of them in detail:

  • A button item. This is the simplest element in the context menu. Usually, a left click opens a window with extended functionality for setting up a program or a window containing some information. There can also be very simple functions. They change something in the appearance of the program interface after a button item has been clicked on.
  • An item with two states of the checkbox type. This element can be used to activate some process or open (make visible) some part of the program interface. When that happens, this item changes its appearance and shows the application user what state it is in.
  • A group of items. In this group only one item can be enabled. This type of control is called a radio button or a switch. We will call it a radio item in this article.
  • An item for calling a context menu. The context menu which was called from the main program file can have items containing one more context menu. After clicking on such an item, a context menu will appear to the right from it.

The MetaEditor code editor also contains the main menu:

Fig. 2. Main menu in the MetaEditor code redactor.

Fig. 2. Main menu in the MetaEditor code editor

Now, we need to identify what classes are required to compose such a complex interface element. It is clear that gathering everything in one class is impractical as studying and then servicing such a class would be rather difficult. Therefore, it makes sense to realize everything the way that the whole complex is assembled from simple parts. Let us decide what parts those will be.

The main menu and context menu consist of several items. The same class can be used for these items in both menu types. A context menu often contains a separation line, which serves for breaking the menu items into categories. Therefore, we can see that we already require at least four classes of code for creating such interface elements as:

  1. A menu item. To create this element, we will develop the CMenuItem class.
  2. The CSeparateLine class will be created for a separation line.
  3. A context menu. The CContextMenu class. This interface element will be assembled from the objects of the CMenuItem class.
  4. The main menu. The CMenuBar class. The same way as in the context menu, the constituent parts will be menu items (CMenuItem).

We have defined the main tasks. From what was said above, it is clear that the most important part for the creation of the main and context menus is a menu item. Therefore, we will continue with creating the CMenuItem class.


Developing the Class for Creating a Menu Item

In the Controls folder, where all other files of the library are located, create the MenuItem.mqh file with the CMenuItem derived class. You can declare virtual methods standard for all controls in it straight away. The base class for it is the CElement class, which we considered in detail in the previous article. We will leave these methods without implementation for now as later in the article we will need to highlight an interesting peculiarity of the compiler.

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

What graphical objects will be used to compose the menu item element? In the main menu, these are usually captions that change their background colors and/or the font colors when the cursor is hovering over them. A context menu item usually has an icon. If an item contains its own context menu, then in the right part of the item area there is an arrow pointing to the right, which is an indication for the user that there is an attached menu. This means that a menu item can be of several different types depending on what it belongs to and what task it is meant to fulfill. Let us enumerate all of its possible constituent parts:

  1. Background.
  2. Label.
  3. Caption.
  4. Indication of the context menu presence.

Fig. 3. Constituent parts of the menu item control.

Fig. 3. Constituent parts of the menu item control.

Add class instances of graphical objects, which are available in the Element.mqh file, and declarations of methods for creating graphical objects to the CMenuItem class. At the moment of attachment of the menu item to the chart, the index number of the menu item has to be passed to the method. This number will be used for forming the names of graphical objects, which will be used for composing a menu item. This will also be used for the identification in the list or items at the moment of clicking on one of them.

class CMenuItem : public CElement
  {
private:
   //--- Objects for creating a menu item
   CRectLabel        m_area;
   CBmpLabel         m_icon;
   CLabel            m_label;
   CBmpLabel         m_arrow;
   //---
public:
   //--- Methods for creating a menu item
   bool              CreateMenuItem(const long chart_id,const int window,const int index_number,const string label_text,const int x,const int y);
   //---
private:
   bool              CreateArea(void);
   bool              CreateIcon(void);
   bool              CreateLabel(void);
   bool              CreateArrow(void);
   //---
  };

As a menu item can be of several different types, there must be a possibility to specify what type it belongs to before creation. We will need an enumeration of the menu item types (ENUM_TYPE_MENU_ITEM).

Add this to the Enums.mqh file where all the enumerations of the library are stored. The ENUM_TYPE_MENU_ITEM enumeration must have the options that have been considered before:

  • MI_SIMPLE — a simple item.
  • MI_HAS_CONTEXT_MENU — an item, containing a context menu.
  • MI_CHECKBOX — a checkbox item.
  • MI_RADIOBUTTON — an item, belonging to a group of radio items.
//+------------------------------------------------------------------+
//| Enumeration of the menu item types                               |
//+------------------------------------------------------------------+
enum ENUM_TYPE_MENU_ITEM
  {
   MI_SIMPLE           =0,
   MI_HAS_CONTEXT_MENU =1,
   MI_CHECKBOX         =2,
   MI_RADIOBUTTON      =3
  };

Add a corresponding field and methods for setting and getting the menu item type to the CMenuItem class:

class CMenuItem : public CElement
  {
private:
   //--- Menu item properties
   ENUM_TYPE_MENU_ITEM m_type_menu_item;
   //---
public:
   //--- Setting and getting the type
   void              TypeMenuItem(const ENUM_TYPE_MENU_ITEM type)   { m_type_menu_item=type;                 }
   ENUM_TYPE_MENU_ITEM TypeMenuItem(void)                     const { return(m_type_menu_item);              }
   //---
  };

The MI_SIMPLE type, that is a simple menu item, will be set by default:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CMenuItem::CMenuItem(void) : m_type_menu_item(MI_SIMPLE)
  {
  }

Methods for setting up the appearance of every graphical object of a menu item have to be created like it was done in the previous article for the form. These methods will have some unique properties. Let us list everything that will be required for this control:

  1. Changing the background color.
  2. Background color when a menu item is unavailable, that is blocked due to the current impossibility of using the item function.
  3. Changing the color of the background frame.
  4. Priority for the left mouse click for all objects must be general and equal to zero with the exception of the background as clicking on it will be tracked against it.
  5. Redefining icons for the label and the indication of the context menu presence.
  6. Methods for managing properties of the text label such as:
    • margins from the zero coordinate of the menu item background;
    • font color;
    • text color when the cursor is hovering over it;
    • text color when the item is blocked.
  7. Variables for tracking the state of a menu item:
    • general state (available/blocked);
    • state of the checkbox;
    • state of the radio point;
    • state of the context menu, if it is attached to this item.
  8. Identifier for a group of radio items. This is necessary as one context menu may contain several groups of radio items. The identifier will allow to understand what group exactly a radio point belongs to.
  9. Index number of a menu item.

Add everything listed above to the CMenuItem class:

class CMenuItem : public CElement
  {
private:
   //--- Background properties
   int               m_area_zorder;
   color             m_area_border_color;
   color             m_area_color;
   color             m_area_color_off;
   color             m_area_color_hover;
   //--- Label properties
   string            m_icon_file_on;
   string            m_icon_file_off;
   //--- Text label properties
   string            m_label_text;
   int               m_label_x_gap;
   int               m_label_y_gap;
   color             m_label_color;
   color             m_label_color_off;
   color             m_label_color_hover;
   //--- Properties of the indication of the context menu
   string            m_right_arrow_file_on;
   string            m_right_arrow_file_off;
   //--- General priority for clicking
   int               m_zorder;
   //--- Available/blocked
   bool              m_item_state;
   //--- Checkbox state
   bool              m_checkbox_state;
   //--- State of the radio button and its identifier
   bool              m_radiobutton_state;
   int               m_radiobutton_id;
   //--- State of the context menu
   bool              m_context_menu_state;
   //---
public:
   //--- Background methods
   void              AreaBackColor(const color clr)                 { m_area_color=clr;                      }
   void              AreaBackColorOff(const color clr)              { m_area_color_off=clr;                  }
   void              AreaBorderColor(const color clr)               { m_area_border_color=clr;               }
   //--- Label methods
   void              IconFileOn(const string file_path)             { m_icon_file_on=file_path;              }
   void              IconFileOff(const string file_path)            { m_icon_file_off=file_path;             }
   //--- Text label methods
   string            LabelText(void)                          const { return(m_label.Description());         }
   void              LabelXGap(const int x_gap)                     { m_label_x_gap=x_gap;                   }
   void              LabelYGap(const int y_gap)                     { m_label_y_gap=y_gap;                   }
   void              LabelColor(const color clr)                    { m_label_color=clr;                     }
   void              LabelColorOff(const color clr)                 { m_label_color_off=clr;                 }
   void              LabelColorHover(const color clr)               { m_label_color_hover=clr;               }
   //--- Methods to indicate the presence of the context menu
   void              RightArrowFileOn(const string file_path)       { m_right_arrow_file_on=file_path;       }
   void              RightArrowFileOff(const string file_path)      { m_right_arrow_file_off=file_path;      }
   //--- Common (1) state of the item and (2) the checkbox item
   void              ItemState(const bool state);
   bool              ItemState(void)                          const { return(m_item_state);                  }
   void              CheckBoxState(const bool flag)                 { m_checkbox_state=flag;                 }
   bool              CheckBoxState(void)                      const { return(m_checkbox_state);              }
   //--- Radio item identifier
   void              RadioButtonID(const int id)                    { m_radiobutton_id=id;                   }
   int               RadioButtonID(void)                      const { return(m_radiobutton_id);              }
   //--- State of the radio item
   void              RadioButtonState(const bool flag)              { m_radiobutton_state=flag;              }
   bool              RadioButtonState(void)                   const { return(m_radiobutton_state);           }
   //--- State of the context menu attached to this item
   bool              ContextMenuState(void)                   const { return(m_context_menu_state);          }
   void              ContextMenuState(const bool flag)              { m_context_menu_state=flag;             }
   //---
  };

In the previous article, it was explained that the library will be structured in a way so that its users do not find themselves in a situation where they do not know what to do. A sequence of actions can be forgotten over time. Before creating a control, it has to be passed a pointer to the form to which it has to be attached. If this is not done, then the program will simply not complete attaching all controls to the form and print a relevant message about it in the journal. This message has to be formed in a way that it is clear on what control the user made an error in the course of his actions.

Include the file containing the form class (CWindow) and declare a pointer with the form type. To store pointer to the form in the class of each control, we will need a corresponding method. Let us name this WindowPointer(). As the only parameter, it accepts an object of the CWindow type by a link. Its task is to store the pointer to the passed object. The GetPointer() function can be used to get an object pointer. The same will have to be done in the class of each control that we will create.

//+------------------------------------------------------------------+
//|                                                     MenuItem.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
//+------------------------------------------------------------------+
//| Class for creating a menu item                                   |
//+------------------------------------------------------------------+
class CMenuItem : public CElement
  {
private:
   //--- Pointer to the form to which the control is attached
   CWindow          *m_wnd;
   //---
public:
   //--- Stores the pointer of the passed form
   void              WindowPointer(CWindow &object)                 { m_wnd=::GetPointer(object);            }
   //---
  };

Now, let us consider the CMenuItem::CreateMenuItem() method for creating a menu item. At the very beginning of the method, we will check the validity of the form pointer to which the element has to be attached to using the CheckPointer() function. If the pointer is not valid, then the program will print a message to the journal and will leave the method having returned false. If the pointer is present, then the element variables are initialized.

In the previous article, we established that every interface element has to have its own identifier and considered in detail how it will be formed. To avoid repetition, I will briefly remind you how it happens.

At the moment of creation of the main form, when a pointer to the main form is added to the element base (the CWndContainer class) the identifier is defined by the counter of the interface elements in the CWndContainer::AddWindow() method. The value of the counter is stored in form classes upon adding each element to the base. As the form pointer is compulsory in every element, the identifier number of the last control is available for each new created interface element through the form pointer. Before creating an element, its identifier will be formed as it is shown in the code below (highlighted in yellow.

Then margins from the edge point of the form are calculated and stored for the element. Access to the form coordinates is available from the form pointer and there is a possibility to calculate the distance from them. The same way margins will be calculated for each object of an element. After that, all element objects are created and a check of the window state is carried out at the end. If the window is minimized, then the element has to be hidden.

//+------------------------------------------------------------------+
//| Creates the menu item element                                    |
//+------------------------------------------------------------------+
bool CMenuItem::CreateMenuItem(const long chart_id,const int subwin,const int index_number,const string label_text,const int x,const int y)
  {
//--- Leave, if there is no pointer to the form
   if(::CheckPointer(m_wnd)==POINTER_INVALID)
     {
      ::Print(__FUNCTION__," > Before creating a menu item, the class has to be passed  "
            "the window pointer: CMenuItem::WindowPointer(CWindow &object)");
      return(false);
     }
//--- Initialization of variables
   m_id           =m_wnd.LastId()+1;
   m_index        =index_number;
   m_chart_id     =chart_id;
   m_subwin       =subwin;
   m_label_text   =label_text;
   m_x            =x;
   m_y            =y;
//--- Margins from the edge point
   CElement::XGap(m_x-m_wnd.X());
   CElement::YGap(m_y-m_wnd.Y());
//--- Creating a menu item
   if(!CreateArea())
      return(false);
   if(!CreateIcon())
      return(false);
   if(!CreateLabel())
      return(false);
   if(!CreateArrow())
      return(false);
//--- If the window is minimized, hide the element after creation
   if(m_wnd.IsMinimized())
      Hide();
//---
   return(true);
  }

In the CMenuItem::Hide() method of hiding the element, all objects have to be hidden, some variables zeroed and colors reset:

//+------------------------------------------------------------------+
//| Hides the menu item                                              |
//+------------------------------------------------------------------+
void CMenuItem::Hide(void)
  {
//--- Leave, if the element is hidden
   if(!CElement::m_is_visible)
      return;
//--- Hide all objects
   for(int i=0; i<ObjectsElementTotal(); i++)
      Object(i).Timeframes(OBJ_NO_PERIODS);
//--- Zeroing variables
   m_context_menu_state=false;
   CElement::m_is_visible=false;
   CElement::MouseFocus(false);
//--- Reset the color
   m_area.BackColor(m_area_color);
   m_arrow.State(false);
  }

Forming the name for graphical objects of the menu item element will be a little more complex that in the CWindow class form. If a menu item is created as a separate element like an independent menu item, which is not a part of the main or context menu (examples are to follow) then there will not be any issues at all as this will have a unique identifier. If this element is created in a group, where there are other similar objects, then one identifier is not enough. This is because otherwise the names of all one-type graphical objects will be the same and as a result only one element will be visible on the chart.

When a menu item is created, the index number of this item will be passed to the CMenuItem::CreateMenuItem() method. Then it will be stored in the field of the m_index_number class, which later will be used for forming the name of every graphical object of the element. Below are constituent parts for the object name of the menu item:

  • Program name.
  • Belonging to the element.
  • Belonging to a part of the element.
  • Index number of the element.
  • Element identifier.

Methods of creating a background, a text label and an indication of the context menu presence do not have any principal differences from those presented in the CWindow class and, therefore, you can study their code in the MenuItem.mqh file attached to this article. Here, we will demonstrate the CMenuItem::CreateIcon() method for creating an icon as an example. In this method the menu item type is checked and depending on the result, a relevant icon is defined.

Simple menu items (MI_SIMPLE) and items, which contain a context menu (MI_HAS_CONTEXT_MENU) may not have an icon at all. If they are not defined by the user, then the program leaves the method having returned true, as this is not an error and an icon is not needed. If for menu items of the checkbox or radio point type icons are not defined, the default ones are used. Icons used in the library code are attached to this article.

The code of the CMenuItem::CreateIcon() method:

//+------------------------------------------------------------------+
//| Creates an item label                                            |
//+------------------------------------------------------------------+
#resource "\\Images\\Controls\\CheckBoxOn_min_gray.bmp"
#resource "\\Images\\Controls\\CheckBoxOn_min_white.bmp"
//---
bool CMenuItem::CreateIcon(void)
  {
//--- If this is a simple item or an item containing a context menu
   if(m_type_menu_item==MI_SIMPLE || m_type_menu_item==MI_HAS_CONTEXT_MENU)
     {
      //--- If the label is not required (icon is not defined), leave
      if(m_icon_file_on=="" || m_icon_file_off=="")
         return(true);
     }
//--- If this is a checkbox
   else if(m_type_menu_item==MI_CHECKBOX)
     {
      //--- If the icon is not defined, set the default one
      if(m_icon_file_on=="")
         m_icon_file_on="Images\\Controls\\CheckBoxOn_min_white.bmp";
      if(m_icon_file_off=="")
         m_icon_file_off="Images\\Controls\\CheckBoxOn_min_gray.bmp";
     }
//--- If this is a radio item     
   else if(m_type_menu_item==MI_RADIOBUTTON)
     {
      //--- If the icon is not defined, set the default one
      if(m_icon_file_on=="")
         m_icon_file_on="Images\\Controls\\CheckBoxOn_min_white.bmp";
      if(m_icon_file_off=="")
         m_icon_file_off="Images\\Controls\\CheckBoxOn_min_gray.bmp";
     }
//--- Forming the object name
   string name=CElement::ProgramName()+"_menuitem_icon_"+(string)CElement::Index()+"__"+(string)CElement::Id();
//--- Object coordinates
   int x =m_x+7;
   int y =m_y+4;
//--- Set the label
   if(!m_icon.Create(m_chart_id,name,m_subwin,x,y))
      return(false);
//--- Set properties
   m_icon.BmpFileOn("::"+m_icon_file_on);
   m_icon.BmpFileOff("::"+m_icon_file_off);
   m_icon.State(m_item_state);
   m_icon.Corner(m_corner);
   m_icon.GetInteger(OBJPROP_ANCHOR,m_anchor);
   m_icon.Selectable(false);
   m_icon.Z_Order(m_zorder);
   m_icon.Tooltip("\n");
//--- Margins from the edge point
   m_icon.XGap(x-m_wnd.X());
   m_icon.YGap(y-m_wnd.Y());
//--- Store the object pointer
   CElement::AddToArray(m_icon);
   return(true);
  }

The CMenuItem::Hide() method for hiding the element has been shown earlier. Now, we are going to implement the CMenuItem::Show() method that will make the element visible. In case a menu item is of the checkbox or radio point type, this method has to consider their current state (enabled/disabled):

//+------------------------------------------------------------------+
//| Makes the menu item visible                                      |
//+------------------------------------------------------------------+
void CMenuItem::Show(void)
  {
//--- Leave, if the element is already visible
   if(CElement::m_is_visible)
      return;
//--- Make all the objects visible
   for(int i=0; i<ObjectsElementTotal(); i++)
      Object(i).Timeframes(OBJ_ALL_PERIODS);
//--- If this is a checkbox, then considering its state
   if(m_type_menu_item==MI_CHECKBOX)
      m_icon.Timeframes((m_checkbox_state)? OBJ_ALL_PERIODS : OBJ_NO_PERIODS);
//--- If this is a radio item, then considering its state
   else if(m_type_menu_item==MI_RADIOBUTTON)
      m_icon.Timeframes((m_radiobutton_state)? OBJ_ALL_PERIODS : OBJ_NO_PERIODS);
//--- Zeroing variables
   CElement::m_is_visible=true;
   CElement::MouseFocus(false);
  }

When all the methods for creating an interface element are implemented, the attachment of the interface to the chart can be tested. Include the CMenuItem class in the WndContainer.mqh file so it is available for use:

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

We can use the EA that we tested in the previous article. In the CProgram class, create an instance of the CMenuItem class and the CProgram::CreateMenuItem1() method for creating a menu item. Margins from the edge point of the form for each created element will be kept handy in macros. When there are a lot of elements, this is a more convenient and quick way to adjust their position in relation to each other than moving from one method implementation to another.

//+------------------------------------------------------------------+
//| Class for creating an application                                |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- Window
   CWindow           m_window;
   //--- Menu item
   CMenuItem         m_menu_item1;
public:
                     CProgram();
                    ~CProgram();
   //--- Initialization/uninitialization
   void              OnInitEvent(void);
   void              OnDeinitEvent(const int reason);
   //--- Timer
   void              OnTimerEvent(void);
   //---
protected:
   //--- Chart event handler
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //---
public:
   //--- Creates the trading panel
   bool              CreateTradePanel(void);
   //---
private:
//--- Creating a form
   bool              CreateWindow(const string text);
//--- Creating a menu item
#define MENU_ITEM1_GAP_X (6)
#define MENU_ITEM1_GAP_Y (25)
   bool              CreateMenuItem1(const string item_text);
  };

To see what an element looks like with a complete set of features, let us create a menu item with an icon, which contains a context menu for a test. We will have two icons - colored and colorless. The colorless icon will be used when the item is blocked (unavailable).

Below is the code of the CProgram::CreateMenuItem1() method. The included resources (icons) are available at the end of this article. At the beginning of the method, the pointer to the form to which the element is to be attached, is stored in the element class. Then coordinates are calculated, required properties of the element are set and the menu item element is attached to the chart.

//+------------------------------------------------------------------+
//| Creates a menu item                                              |
//+------------------------------------------------------------------+
#resource "\\Images\\Controls\\bar_chart.bmp"
#resource "\\Images\\Controls\\bar_chart_colorless.bmp"
//---
bool CProgram::CreateMenuItem1(string item_text)
  {
//--- Store the window pointer
   m_menu_item1.WindowPointer(m_window);
//--- Coordinates  
   int x=m_window.X()+MENU_ITEM1_GAP_X;
   int y=m_window.Y()+MENU_ITEM1_GAP_Y;
//--- Set up properties before creation
   m_menu_item1.XSize(193);
   m_menu_item1.YSize(24);
   m_menu_item1.TypeMenuItem(MI_HAS_CONTEXT_MENU);
   m_menu_item1.IconFileOn("Images\\Controls\\bar_chart.bmp");
   m_menu_item1.IconFileOff("Images\\Controls\\bar_chart_colorless.bmp");
   m_menu_item1.LabelColor(clrWhite);
   m_menu_item1.LabelColorHover(clrWhite);
//--- Creating a menu item
   if(!m_menu_item1.CreateMenuItem(m_chart_id,m_subwin,0,item_text,x,y))
      return(false);
//---
   return(true);
  }

Now, a call of the method for creating a menu item can be added in the CProgram::CreateTradePanel() method:

//+------------------------------------------------------------------+
//| Creates the trading panel                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Creating a form for controls
   if(!CreateWindow("EXPERT PANEL"))
      return(false);
//--- Creating controls:
//    Menu item
   if(!CreateMenuItem1("Menu item"))
      return(false);
//--- Redrawing of the chart
   m_chart.Redraw();
   return(true);
  }

An attentive reader will have a question: "How will the element pointer get into the base?". This is a good question as this has not been implemented yet and the element pointer has not been stored in the base yet. Earlier, I mentioned that I would demonstrate an interesting peculiarity of the compiler. Now, everything is ready for it. Methods for managing the element and handling its events are currently not implemented in the CMenuItem class and there is no pointer in the base. The compilation of the file with the CMenuItem class did not indicate any errors. Try to compile the file of the EA and you will receive an error message specifying the methods for which their implementation is required (see the screenshot below).

Fig. 4. Message about absence of method implementation

Fig. 4. Message about absence of method implementation

When the file was compiled with the CMenuItem class, no errors were encountered because in this class at the current level of development, methods without implementation are not called. The element base at the moment have only the form pointer. It looks like the call of the methods that you see in the screenshot above is carried out only for the form (CWindow) in the CWndEvents class in the loops of the CheckElementsEvents(), MovingWindow(), CheckElementsEventsTimer() and Destroy() methods. Element pointers are stored in the array of the CElement type, which means that the call is carried out through the CElement base class where these methods are declared as virtual.

Since in the WndContainer.mqh file in the base two files with the Window.mqh and MenuItem.mqh elements are included and their classes are derived from the CElement class, the compiler identified all classes derived from it and demands implementation of the called methods even if one of them does not have a direct call. Let us create the required methods in the CMenuItem class. We will also create a method that will allow us to add control pointers to the base in the CWndContainer class.

At this stage, the CMenuItem::ChangeObjectsColor() method can be added for changing the color of the element objects when the mouse cursor is hovering over it. In this method, the type and state of the menu item element (available/blocked) have to be considered. At the beginning of this method, there must be a check if this item contains a context menu and if the latter is currently enabled. The reason being that when a context menu is enabled, the management is passed to it. Besides, the color of the item it was called from must be recorded.

Later, we will also need a method of the focus color reset in the menu item, therefore let us create the CMenuItem::ResetColors() method.

class CMenuItem : public CElement
  {
public:
   //--- Changing the color of the control objects
   void              ChangeObjectsColor(void);
   //--- Reset color
   void              ResetColors(void);
   //---
  };
//+------------------------------------------------------------------+
//| Changing the object color when the cursor is hovering over it    |
//+------------------------------------------------------------------+
void CMenuItem::ChangeObjectsColor(void)
  {
//--- Leave, if this item has a context menu and it is enabled
   if(m_type_menu_item==MI_HAS_CONTEXT_MENU && m_context_menu_state)
      return;
//--- Code block for simple items and items containing a context menu
   if(m_type_menu_item==MI_HAS_CONTEXT_MENU || m_type_menu_item==MI_SIMPLE)
     {
      //--- If there is a focus
      if(CElement::MouseFocus())
        {
         m_icon.State(m_item_state);
         m_area.BackColor((m_item_state)? m_area_color_hover : m_area_color_off);
         m_label.Color((m_item_state)? m_label_color_hover : m_label_color_off);
         if(m_item_state)
            m_arrow.State(true);
        }
      //--- If there is no focus
      else
        {
         m_arrow.State(false);
         m_area.BackColor(m_area_color);
         m_label.Color((m_item_state)? m_label_color : m_label_color_off);
        }
     }
//--- Block code for checkbox items and radio items
   else if(m_type_menu_item==MI_CHECKBOX || m_type_menu_item==MI_RADIOBUTTON)
     {
      m_icon.State(CElement::MouseFocus());
      m_area.BackColor((CElement::MouseFocus())? m_area_color_hover : m_area_color);
      m_label.Color((CElement::MouseFocus())? m_label_color_hover : m_label_color);
     }
  }
//+------------------------------------------------------------------+
//| Reset the item color                                             |
//+------------------------------------------------------------------+
void CMenuItem::ResetColors(void)
  {
   CElement::MouseFocus(false);
   m_area.BackColor(m_area_color);
   m_label.Color(m_label_color);
  }

Methods for moving and deleting objects are implemented in a similar way as it was done in the CWindow class and, therefore, there is no point in repeating them here. You can find their code in the files attached to this article. That is why add the minimum required code (see the code below) to the CMenuItem::OnEvent() and CMenuItem::OnEventTimer() methods and compile the files of the library and the EA. Now, there will not be any errors reporting the necessity of the method implementation.

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CMenuItem::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- If the control is not hidden
      if(!CElement::m_is_visible)
         return;
      //--- Identify the focus
      int x=(int)lparam;
      int y=(int)dparam;
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      return;
     }  
  }
//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CMenuItem::OnEventTimer(void)
  {
//--- Changing the color of the form objects
   ChangeObjectsColor();
  }


Test of Attaching a Menu Item

When you load the EA on to the chart, you will see a form with the menu item element as in the screenshot below. You cannot see it here as the default color of the element background matches the color of the form background. You can already use the class functionality for changing many properties of the objects - both the form and the menu item element, if required.

Fig. 5. Test of attaching the menu item element to the chart.

Fig. 5. Test of attaching the menu item element to the chart

The element is attached to the chart but if you try to move the form, the element will stay in the same location and if you hover the cursor over, its appearance will not change. Let us create a method for adding element pointers to the base. This is how properties of all elements can be managed by calling each of them in one loop.

Let us name this method CWndContainer::AddToElementsArray(). This will have two parameters: (1) number of the form index, to which the element is supposed to be attached (2) element object, a pointer to which has to be saved in the base. At the beginning of this method, there will be a check if the base contains a form or forms. If there are none, then a message will be printed in the journal that before attempting to attach the element to the form, this has to be added to the base first. Then there will be a check for exceeding the array size.

If there are no issues, then (1) the pointer is added to the array of the form to which it is attached, (2) element objects are added to the common array of objects, (3) the last identifier of this element is stored in all forms and (4) the element counter is increased by one. This is not the final version of the method, we will get back to this later. The code of the current version is presented below:

//+------------------------------------------------------------------+
//| Adds a pointer to the element array                              |
//+------------------------------------------------------------------+
void CWndContainer::AddToElementsArray(const int window_index,CElement &object)
  {
//--- If the base does not contain forms for controls
   if(ArraySize(m_windows)<1)
     {
      Print(__FUNCTION__," > Before creating a control, create a form  "
            "and add it to the base using the CWndContainer::AddWindow(CWindow &object) method.");
      return;
     }
//--- If there is a request for a non-existent form
   if(window_index>=ArraySize(m_windows))
     {
      Print(PREVENTING_OUT_OF_RANGE," window_index: ",window_index,"; ArraySize(m_windows): ",ArraySize(m_windows));
      return;
     }
//--- Add to the common array of elements
   int size=ArraySize(m_wnd[window_index].m_elements);
   ArrayResize(m_wnd[window_index].m_elements,size+1);
   m_wnd[window_index].m_elements[size]=GetPointer(object);
//--- Add element objects to the common array of objects
   AddToObjectsArray(window_index,object);
//--- Store the id of the last element in all forms
   int windows_total=ArraySize(m_windows);
   for(int w=0; w<windows_total; w++)
      m_windows[w].LastId(m_counter_element_id);
//--- Increase the counter of element identifiers
   m_counter_element_id++;
  }

The current version of the CWndContainer::AddToElementsArray() method is already sufficient for testing the EA, which we started to work on earlier. At the end of the CProgram::CreateMenuItem() method, after creating an element, add a line of code as shown in the shortened version of the method below.

//+------------------------------------------------------------------+
//| Creates a menu item                                              |
//+------------------------------------------------------------------+
bool CProgram::CreateMenuItem(string item_text)
  {
//--- Store the window pointer
//--- Coordinates  
//--- Set up properties before creation
//--- Creating a menu item
//--- Add the element pointer to the base
   CWndContainer::AddToElementsArray(0,m_menu_item);
   return(true);
  }

If the files where changes were introduced are compiled and the EA is loaded on to the chart, then when the form is moved, the menu item element will be moving together with it and all its objects will change their appearance when the mouse cursor hovers over it:

Fig. 6. Test of the menu item element as a part of the graphical interface

Fig. 6. Test of the menu item element as a part of the graphical interface


Further Development of the Library Main Classes

If the form is minimized now, then the menu item will not be hidden as expected. How can hiding elements attached to the form be implemented? The management of the chart events is currently organized in the CWndEvents class, which has access to the base of all elements of its own base class CWndContainer. Is there an indication that a minimizing or maximizing button was pressed? For such cases, MQL has the EventChartCustom() function that can generate custom events.

Now, when the button for minimizing the form is clicked on, the program will track the CHARTEVENT_OBJECT_CLICK event in the OnEvent() handler of the CWindow class and, having verified the value of the string parameter (sparam) of this event against the name of the graphical object, will call the CWindow::RollUp() method. This is the time when a message can be sent to the queue of the event stream and received in the handler of the CWndEvents class. As the CWndEvents class has access to all the elements, the CElement::Hide() method of each element can be called in the loop. Same must be done for the form maximization event, making all form elements visible using their methods CElement::Show().

Every custom event requires its unique identifier. Add the identifiers for the minimization/maximization of the form to the Defines.mqh file:

//--- Events
#define ON_WINDOW_UNROLL          (1) // Form maximization
#define ON_WINDOW_ROLLUP          (2) // Form minimization

At the end of the CWindow::RollUp() and CWindow::Unroll() methods call the EventChartCustom() function having passed to it:

  1. The chart identifier.
  2. The custom event identifier.
  3. The element identifier as the third parameter (lparam).
  4. The number of the chart sub-window where the program is located as the fourth parameter (dparam).

Third and fourth parameters are required for the additional check in case this event is sent by another element or another program.

Below are shortened versions of the CWindow::RollUp() and CWindow::Unroll() methods containing only what has to be added to them (all comments are kept):

//+------------------------------------------------------------------+
//| Minimizes the window                                             |
//+------------------------------------------------------------------+
void CWindow::RollUp(void)
  {
//--- Change the button
//--- Set and store the size
//--- Disable the button
//--- Minimized state of the form
//--- If this is an indicator with a set height and with the sub-window minimization mode,
//    set the size of the indicator sub-window
//--- Send a message about it
   ::EventChartCustom(m_chart_id,ON_WINDOW_ROLLUP,CElement::Id(),m_subwin,"");
  }
//+------------------------------------------------------------------+
//| Maximizes the window                                             |
//+------------------------------------------------------------------+
void CWindow::Unroll(void)
  {
//--- Change the button
//--- Set and store the size
//--- Disable the button
//--- Maximized state of the form
//--- If this is an indicator with a set height and with the sub-window minimization mode,
//    set the size of the indicator sub-window
//--- Send a message about it
   ::EventChartCustom(m_chart_id,ON_WINDOW_UNROLL,CElement::Id(),m_subwin,"");
  }

Now, we need to create methods in the CWndEvents class, which will be handling these custom events. We will name them the same way as macros for identifiers. Below is the code for the declaration and implementation of the methods in the CWndEvents class:

class CWndEvents : public CWndContainer
  {
private:
   //--- Minimizing/maximizing the form
   bool              OnWindowRollUp(void);
   bool              OnWindowUnroll(void);
   //---
  };
//+------------------------------------------------------------------+
//| ON_WINDOW_ROLLUP event                                           |
//+------------------------------------------------------------------+
bool CWndEvents::OnWindowRollUp(void)
  {
//--- If the signal is to minimize the form
   if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_ROLLUP)
      return(false);
//--- If the window identifier and the sub-window number match
   if(m_lparam==m_windows[0].Id() && (int)m_dparam==m_subwin)
     {
      int elements_total=CWndContainer::ElementsTotal(0);
      for(int e=0; e<elements_total; e++)
        {
         //--- Hide all elements except the form
         if(m_wnd[0].m_elements[e].ClassName()!="CWindow")
            m_wnd[0].m_elements[e].Hide();
        }
     }
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| ON_WINDOW_UNROLL event                                           |
//+------------------------------------------------------------------+
bool CWndEvents::OnWindowUnroll(void)
  {
//--- If the signal is to maximize the form
   if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_UNROLL)
      return(false);
//--- If the window identifier and the sub-window number match
   if(m_lparam==m_windows[0].Id() && (int)m_dparam==m_subwin)
     {
      int elements_total=CWndContainer::ElementsTotal(0);
      for(int e=0; e<elements_total; e++)
        {
         //--- Make all elements visible except the form and the
         //    drop-down ones
         if(m_wnd[0].m_elements[e].ClassName()!="CWindow")
            if(!m_wnd[0].m_elements[e].IsDropdown())
               m_wnd[0].m_elements[e].Show();
        }
     }
//---
   return(true);
  }

The CWindow::Show() method has not been implemented yet in the form class. As this method will be called in all elements in a loop, its implementation is compulsory, even if a condition does not let the program reach the CWindow class as it is shown in the code above.

Code of the CWindow::Show() method:

//+------------------------------------------------------------------+
//| Shows the window                                                 |
//+------------------------------------------------------------------+
void CWindow::Show(void)
  {
//--- Make all the objects visible
   for(int i=0; i<ObjectsElementTotal(); i++)
      Object(i).Timeframes(OBJ_ALL_PERIODS);
//--- Visible state
   CElement::m_is_visible=true;
//--- Zeroing the focus
   CElement::MouseFocus(false);
   m_button_close.MouseFocus(false);
   m_button_close.State(false);
  }

We will call these methods in the CWndEvents::ChartEventCustom() method:

//+------------------------------------------------------------------+
//| CHARTEVENT_CUSTOM event                                          |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- If the signal is to minimize the form
   if(OnWindowRollUp())
      return;
//--- If the signal is to maximize the form
   if(OnWindowUnroll())
      return;
  }

From now on, all methods that handle custom events will be located in the CWndEvents::ChartEventCustom() method.

In its turn, the call of CWndEvents::ChartEventCustom() has to be placed in the CWndEvents::ChartEvent() method before the methods where chart events are handled:

//+------------------------------------------------------------------+
//| Handling program events                                          |
//+------------------------------------------------------------------+
void CWndEvents::ChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Leave, if the array is empty
   if(CWndContainer::WindowsTotal()<1)
      return;
//--- Initialization of the event parameter fields
   InitChartEventsParams(id,lparam,dparam,sparam);
//--- Custom event
   ChartEventCustom();
//--- Checking events of the interface elements
   CheckElementsEvents();
//--- Mouse movement event
   ChartEventMouseMove();
//--- The chart properties change event
   ChartEventChartChange();
  }

After compiling files that underwent changes and the main program file, load it on to the chart. Now, when the form is minimized, the menu item element will be hidden and become visible when the form is maximized.

We have completed the development of the main part of the CMenuItem class. Now, we only have to set up the event handler of this control. We will get back to this after we have implemented the class for creating a context menu so all changes can be tested consistently and fully. Before that, we will develop another interface element which is a part of a context menu - a separation line.


Conclusion

In this article we have discussed in detail the process of creating the menu item control. We also introduced necessary additions to the form class for controls (CWindow) and the main class of event handling (CWndEvents). In the next article we will create classes for creating a separation line and a context menu.

You can find and download archives with the library files at the current stage of development, icons and files of the programs (the EA, indicators and the script) considered in this article for testing in the MetaTrader 4 and MetaTrader 5 terminals. 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 from the list below or ask your question in the comments of this article.

List of the articles (chapters) of the second part:

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

Attached files |
Last comments | Go to discussion (1)
Thiago Duarte
Thiago Duarte | 10 Apr 2020 at 04:17
I am getting these errors, and dont know why :(

Fuzzy logic to create manual trading strategies Fuzzy logic to create manual trading strategies
This article suggests the ways of improving manual trading strategy by applying fuzzy set theory. As an example we have provided a step-by-step description of the strategy search and the selection of its parameters, followed by fuzzy logic application to blur overly formal criteria for the market entry. This way, after strategy modification we obtain flexible conditions for opening a position that has a reasonable reaction to a market situation.
Adding a control panel to an indicator or an Expert Advisor in no time Adding a control panel to an indicator or an Expert Advisor in no time
Have you ever felt the need to add a graphical panel to your indicator or Expert Advisor for greater speed and convenience? In this article, you will find out how to implement the dialog panel with the input parameters into your MQL4/MQL5 program step by step.
Graphical Interfaces II: the Separation Line and Context Menu Elements (Chapter 2) Graphical Interfaces II: the Separation Line and Context Menu Elements (Chapter 2)
In this article we will create the separation line element. It will be possible to use it not only as an independent interface element but also as a part of many other elements. After that, we will have everything required for the development of the context menu class, which will be also considered in this article in detail. Added to that, we will introduce all necessary additions to the class, which is the base for storing pointers to all the elements of the graphical interface of the application.
Graphical Interfaces I: Testing Library in Programs of Different Types and in the MetaTrader 4 Terminal (Chapter 5) Graphical Interfaces I: Testing Library in Programs of Different Types and in the MetaTrader 4 Terminal (Chapter 5)
In the previous chapter of the first part of the series about graphical interfaces, the form class was enriched by methods which allowed managing the form by pressing its controls. In this article, we will test our work in different types of MQL program such as indicators and scripts. As the library was designed to be cross-platform so it could be used in all MetaTrader platforms, we will also test it in MetaTrader 4.