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

Graphical Interfaces II: The Main Menu Element (Chapter 4)

MetaTrader 5Examples | 31 March 2016, 16:16
11 135 1
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. A full list of the links to the articles is at the end of each chapter of the series. There you can also find and download a complete version of the library at the current stage of development. The files must be located in the same directories as in the archive.    

This is the final chapter of the second part of the series about graphical interfaces. Here, we are going to consider the creation of the main menu. The development of this control and setting up handlers of the library classes for correct reaction to the user's actions will be demonstrated here. We will also discuss how to attach context menus to the items of the main menu. In addition, we will speak about blocking the elements that are currently inactive.

 


Developing the Class for Creating the Main Menu

Classes for creating all elements for building the main menu of the program were developed in the previous three chapters. We have the following classes:

  • CMenuItem – menu item.
  • CSeparateLine – separation line.
  • CContextMenu – context menu.

Create the MenuBar.mqh file in the Controls folder located in the directory containing the files of all elements. In this file, include the file containing the base class, file with the form class and files of all compound elements that will constitute it:

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

The basic objects of the main menu are the background and menu items. Context menus will be attached to the items of the main menu via pointers.

Fig. 1. Basic pats of the main menu.

Fig. 1. Basic pats of the main menu.


The initial form of the CMenuBar class with instances of necessary classes, pointers and virtual methods standard for each element is presented in the code below.

//+------------------------------------------------------------------+
//| Class for creating the main menu                                 |
//+------------------------------------------------------------------+
class CMenuBar : public CElement
  {
private:
   //--- Pointer to the form to which the element is attached
   CWindow          *m_wnd;
   //--- Objects for creating a menu item
   CRectLabel        m_area;
   CMenuItem         m_items[];
   //--- Array of context menu pointers
   CContextMenu     *m_contextmenus[];
   //---
public:
                     CMenuBar(void);
                    ~CMenuBar(void);
   //--- Stores the form pointer
   void              WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); }

   //--- Chart event handler
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Moving the element
   virtual void      Moving(const int x,const int y);
   //--- (1) Show, (2) hide, (3) reset, (4) delete
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) Setting, (2) resetting of priorities for left clicking
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
   //---
  };

Similar to any other interface element, there must be a possibility to set up the appearance of the main menu. Fields and methods will be created specifically for that purpose and will allow to set up:

  • background properties of the main menu;
  • general properties of the menu items.

Besides, methods for setting and getting the current state of the main menu will be required. All the fields have to be initialized in the constructor by default values. The default height of the main menu will be 22 pixels. The height of the menu items will be calculated automatically in relation to the background height of the main menu. This value, however, can be altered before attaching the element to the chart with the help of the CElement::YSize() method of the base class. The background width of the main menu is equal to the width of the form to which this is attached. This is why we will make the calculation of this parameter automated when attaching the element to the chart.

class CMenuBar : public CElement
  {
private:
   //--- Background properties
   int               m_area_zorder;
   color             m_area_color;
   color             m_area_color_hover;
   color             m_area_border_color;
   //--- General properties of menu items
   int               m_item_y_size;
   color             m_item_color;
   color             m_item_color_hover;
   color             m_item_border_color;
   int               m_label_x_gap;
   int               m_label_y_gap;
   color             m_label_color;
   color             m_label_color_hover;
   //--- State of the main menu
   bool              m_menubar_state;
   //---
public:
   //--- Color of (1) the background and (2) the background frame of the main menu
   void              MenuBackColor(const color clr)       { m_area_color=clr;                    }
   void              MenuBorderColor(const color clr)     { m_area_border_color=clr;             }
   //--- (1) background color, (2) background color when the cursor is hovering over it and (3) frame color of the main menu items
   void              ItemBackColor(const color clr)       { m_item_color=clr;                    }
   void              ItemBackColorHover(const color clr)  { m_item_color_hover=clr;              }
   void              ItemBorderColor(const color clr)     { m_item_border_color=clr;             }
   //--- Margins of the text label from the edge point of the menu item background
   void              LabelXGap(const int x_gap)           { m_label_x_gap=x_gap;                 }
   void              LabelYGap(const int y_gap)           { m_label_y_gap=y_gap;                 }
   //--- (1) Standard and (2) in-focus text color
   void              LabelColor(const color clr)          { m_label_color=clr;                   }
   void              LabelColorHover(const color clr)     { m_label_color_hover=clr;             }
   //--- State of the main menu
   void              State(const bool state);
   bool              State(void)                    const { return(m_menubar_state);             }
   //---
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CMenuBar::CMenuBar(void) : m_menubar_state(false),
                           m_area_zorder(0),
                           m_area_color(C'240,240,240'),
                           m_area_border_color(clrSilver),
                           m_item_color(C'240,240,240'),
                           m_item_color_hover(C'51,153,255'),
                           m_item_border_color(C'240,240,240'),
                           m_label_x_gap(15),
                           m_label_y_gap(3),
                           m_label_color(clrBlack),
                           m_label_color_hover(clrWhite)
  {
//--- Store the name of the element class in the base class
   CElement::ClassName(CLASS_NAME);
//--- Default height of the main menu
   m_y_size=22;
  }
//+------------------------------------------------------------------+
//| Setting the state of the main menu                               |
//+------------------------------------------------------------------+
void CMenuBar::State(const bool state)
  {
   if(state)
      m_menubar_state=true;
   else
     {
      m_menubar_state=false;
      //--- Iterate over all items of the main menu for
      //    setting the status of disabled context menus
      int items_total=ItemsTotal();
      for(int i=0; i<items_total; i++)
         m_items[i].ContextMenuState(false);
      //--- Unblock the form
      m_wnd.IsLocked(false);
     }
  }

Setting up unique properties for each menu item requires array. Unique properties are:

  • width of the menu item;
  • displayed text.

These properties will be set up before attaching the main menu to the chart, that is during its formation at the moment of adding every item. For that, the CMenuBar::AddItem() method will be used similar to the one created earlier in the CContextMenu class. The only difference between them is passed (set) parameters. 

Let us create the CMenuBar::AddContextMenuPointer() method for attaching context menus to every item of the main menu. The index of the main menu item and the object of the context menu are to be passed to this method. The pointer to the object of the context menu will be stored in the m_contextmenus[] array.

class CMenuBar : public CElement
  {
private:
   //--- Arrays of unique properties of menu items:
   //    (1) Width, (2) text
   int               m_width[];
   string            m_label_text[];
   //---
public:
   //--- Adds a menu item with specified properties before creation of the main menu
   void              AddItem(const int width,const string text);
   //--- Attaches the passed context menu to the specified item of the main menu
   void              AddContextMenuPointer(const int index,CContextMenu &object);
   //---
  };
//+------------------------------------------------------------------+
//| Adds a menu item                                                 |
//+------------------------------------------------------------------+
void CMenuBar::AddItem(const int width,const string text)
  {
//--- Increase the size of the arrays by one element  
   int array_size=::ArraySize(m_items);
   ::ArrayResize(m_items,array_size+1);
   ::ArrayResize(m_contextmenus,array_size+1);
   ::ArrayResize(m_width,array_size+1);
   ::ArrayResize(m_label_text,array_size+1);
//--- Store the values of passed parameters
   m_width[array_size]      =width;
   m_label_text[array_size] =text;
  }
//+------------------------------------------------------------------+
//| Adds the context menu pointer                                    |
//+------------------------------------------------------------------+
void CMenuBar::AddContextMenuPointer(const int index,CContextMenu &object)
  {
//--- Check for exceeding the range
   int size=::ArraySize(m_contextmenus);
   if(size<1 || index<0 || index>=size)
      return;
//--- Store the pointer
   m_contextmenus[index]=::GetPointer(object);
  }

We will also require the methods for getting the pointer to the main menu item and the pointer to one of the context menus attached to the items. Each of these methods will contain the check of the array size and adjustment of the index in case the array range has been exceeded. In addition to that, the iteration over items of the main menu and context menus will be carried out a lot. This is why methods for getting the sizes of their arrays are required.

class CMenuBar : public CElement
  {
public:
   //--- (1) Getting the pointer to the specified menu item, (2) getting the pointer to the specified context menu
   CMenuItem        *ItemPointerByIndex(const int index);
   CContextMenu     *ContextMenuPointerByIndex(const int index);

   //--- Quantity of (1) menu items and (2) context menus
   int               ItemsTotal(void)               const { return(::ArraySize(m_items));        }
   int               ContextMenusTotal(void)        const { return(::ArraySize(m_contextmenus)); }
   //---
  };
//+------------------------------------------------------------------+
//| Returns a menu item pointer by the index                         |
//+------------------------------------------------------------------+
CMenuItem *CMenuBar::ItemPointerByIndex(const int index)
  {
   int array_size=::ArraySize(m_items);
//--- If the main menu does not contain any item, report
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > This method is to be called, "
      "when the main menu contains at least one item!");
     }
//--- Adjustment in case the range has been exceeded
   int i=(index>=array_size)? array_size-1 : (index<0)? 0 : index;
//--- Return the pointer
   return(::GetPointer(m_items[i]));
  }
//+------------------------------------------------------------------+
//| Returns the context menu pointer by the index                    |
//+------------------------------------------------------------------+
CContextMenu *CMenuBar::ContextMenuPointerByIndex(const int index)
  {
   int array_size=::ArraySize(m_contextmenus);
//--- If the main menu does not contain any item, report
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > This method is to be called, "
      "when the main menu contains at least one item!");
     }
//--- Adjustment in case the range has been exceeded
   int i=(index>=array_size)? array_size-1 : (index<0)? 0 : index;
//--- Return the pointer
   return(::GetPointer(m_contextmenus[i]));
  }

The process of building the main menu does not have any fundamental differences from the creation of the context menu in the CContextMenu class. Actually, the creation of the main menu is a little simpler as the main menu does not require the pointer to the previous node. This does not contain separation lines either. We will not consider the code of those methods to save space in the article. You can look them up in the MenuBar.mqh file attached at the end of the article.

Earlier, to ensure that the context menu items get to the base of all elements in the CWndContainer class, the special method AddContextMenuElements() was written. This is called in the CWndContainer::AddToElementsArray() main method of adding elements to the base. The same method must be written for the main menu element, otherwise the menu items will not move together with the form and will not change their color when the mouse cursor is hovering over them. 

Below is a short list of actions to perform for the compound elements of some main element to get to the base and the main element pointer to get to the private array.

  • Include the file with the element class to the WndContainer.mqh file.
  • Add the element array to the WindowElements structure.
  • Add the method for getting the number of the main menus in the private array.
  • Declare and implement the private method for storing the pointers to the elements that are a part of another main element.
  • Place the call of the new method in the common method which is dedicated to being used in the application class for adding the main element pointer to the base.

Below is a shortened version of the code from the WndContainer.mqh file which presents only what needs to be added to it:

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "MenuBar.mqh"
//+------------------------------------------------------------------+
//| Class for storing all interface objects                          |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- Structure of the element arrays
   struct WindowElements
     {
      //--- Main menu array
      CMenuBar         *m_menu_bars[];
     };
   //---
public:
   //--- Number of main menus
   int               MenuBarsTotal(const int window_index);
   //---
private:
   //--- Stores pointers to the main menu elements in the base
   bool              AddMenuBarElements(const int window_index,CElement &object);
  };
//+------------------------------------------------------------------+
//| Returns the number of main menus by the specified window index   |
//+------------------------------------------------------------------+
int CWndContainer::MenuBarsTotal(const int window_index)
  {
   if(window_index>=::ArraySize(m_wnd))
     {
      ::Print(PREVENTING_OUT_OF_RANGE);
      return(WRONG_VALUE);
     }
//---
   return(::ArraySize(m_wnd[window_index].m_menu_bars));
  }
//+------------------------------------------------------------------+
//| 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 the request is for a non-existent form
//--- Add to the common array of elements
//--- Add element objects to the common array of objects
//--- Store the id of the last element in all forms
//--- Increase the counter of element identifiers
//--- Stores the pointers to the context menu objects in the base
//--- Stores the pointers to the main menu objects in the base
   if(AddMenuBarElements(window_index,object))
      return;
  }
//+------------------------------------------------------------------+
//| Stores the pointers to the main menu objects in the base         |
//+------------------------------------------------------------------+
bool CWndContainer::AddMenuBarElements(const int window_index,CElement &object)
  {
//--- Leave, if this is not the main menu
   if(object.ClassName()!="CMenuBar")
      return(false);
//--- Get the main menu pointer
   CMenuBar *mb=::GetPointer(object);
//--- Store the pointers to its objects in the base
   int items_total=mb.ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Increasing the element array
      int size=::ArraySize(m_wnd[window_index].m_elements);
      ::ArrayResize(m_wnd[window_index].m_elements,size+1);
      //--- Getting the menu item pointer
      CMenuItem *mi=mb.ItemPointerByIndex(i);
      //--- Store the pointer in the array
      m_wnd[window_index].m_elements[size]=mi;
      //--- Add pointers to all the objects of a menu item to the common array
      AddToObjectsArray(window_index,mi);
     }
//--- Add the pointer to the private array
   AddToRefArray(mb,m_wnd[window_index].m_menu_bars);
   return(true);
  }

 

 

Test of Setting Up the Main Menu

At this stage we can test setting up the main menu. We are going to build this element from three items. The width and displayed text for each of the elements are going to be set up while the rest of the properties are going to stay as default.

Add the code for creating the main menu to the CProgram application class as shown in the code below. Adjust the coordinates of other elements that were attached to the form prior to that. And finally, place the call of the CProgram::CreateMenuBar() new method in the main method for creating the graphical interface.

class CProgram : public CWndEvents
  {
private:
   //--- Main menu
   CMenuBar          m_menubar;
   //---
private:
   //---
#define MENUBAR_GAP_X    (1)
#define MENUBAR_GAP_Y    (20)
   bool              CreateMenuBar(void);
   //---
#define MENU_ITEM1_GAP_X (6)
#define MENU_ITEM1_GAP_Y (45)
   //---
#define SEP_LINE_GAP_X   (6)
#define SEP_LINE_GAP_Y   (75)
   //---
  };
//+------------------------------------------------------------------+
//| Creates the trading panel                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Creating a form for controls
//--- Creating controls:
//    Main menu
   if(!CreateMenuBar())
      return(false);
//--- Menu item
//--- Separation line
//--- Redrawing of the chart
   return(true);
  }
//+------------------------------------------------------------------+
//| Creates the main menu                                            |
//+------------------------------------------------------------------+
bool CProgram::CreateMenuBar(void)
  {
//--- Three items in the main menu
#define MENUBAR_TOTAL 3
//--- Store the window pointer
   m_menubar.WindowPointer(m_window);
//--- Coordinates
   int x=m_window.X()+MENUBAR_GAP_X;
   int y=m_window.Y()+MENUBAR_GAP_Y;
//--- Arrays with unique properties of each item
   int    width[MENUBAR_TOTAL] ={50,55,53};
   string text[MENUBAR_TOTAL]  ={"File","View","Help"};
//--- Add items to the main menu
   for(int i=0; i<MENUBAR_TOTAL; i++)
      m_menubar.AddItem(width[i],text[i]);
//--- Create a control
   if(!m_menubar.CreateMenuBar(m_chart_id,m_subwin,x,y))
      return(false);
//--- Add the object to the common array of the object groups
   CWndContainer::AddToElementsArray(0,m_menubar);
   return(true);
  }

Compile the files and load the EA on to the chart. The result should be as in the screenshot below. The main menu will be moving together with the form and its items will change their color when the mouse cursor is hovering over them.

Fig. 2. Test of the main menu element.

Fig. 2. Test of the main menu element.

 

 


Blocking Inactive Controls

Before creating context menus and attaching them to the items of the main menu, our library requires functionality that will block the form and other elements when one of the elements is activated. Why does this have to be done? Activated elements here are the ones that are made visible (called) through other elements and are hidden when they are not needed any more. For instance, drop-down lists, context menus, calendars and the likes belong to this group. Try to activate any context menu or a drop-down list in the MetaTrader trading terminal. You will see that when an element of this type is activated, other controls in the whole terminal become unavailable. It is manifested, for example, when they do not change their color when the mouse cursor is hovering over them. The reason for such blocking is to exclude a situation when the cursor reacts to an element that is currently obscured by a drop-down list.

All you need to do to replicate this behavior is to block the form to which the activated control belongs. Any other element on this form has access to it via the pointer and, therefore, it can be found out what state it is in at any time. The principle here is very simple - if the form is blocked, then the method for changing the element color does not need to be called.

Add a special field and methods for setting and getting the state of the form (blocked/unblocked) to the CWindow class for creating a form as shown in the code below. The m_is_locked field in the constructor must be initialized by the false value. This means that the form must be unblocked by default. We can add the condition defining that the color of the form and its elements must take place only when the form is not blocked.

class CWindow : public CElement
  {
private:
   //--- Status of a blocked window
   bool              m_is_locked;
   //---
public:
   //--- Status of a blocked window
   bool              IsLocked(void)                                    const { return(m_is_locked);                }
   void              IsLocked(const bool flag)                               { m_is_locked=flag;                   }
   //---
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CWindow::CWindow(void) : m_is_locked(false)
  {
  }
//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CWindow::OnEventTimer(void)
  {
//--- If the window is not blocked
   if(!m_is_locked)
     {
      //--- Changing the color of the form objects
      ChangeObjectsColor();
     }
  }

As far as other controls are concerned, we currently have only one clickable element that we can test the functionality on - a menu item. Changing the color of the menu item when the mouse cursor is hovering over it will depend on the state of the form to which it is attached. Therefore, such a check must be placed also in its class CMenuItem as shown in the code below.

//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CMenuItem::OnEventTimer(void)
  {
//--- If the window is available
   if(!m_wnd.IsLocked())
     {
      //--- If the status of a disabled context menu
      if(!m_context_menu_state)
         //--- Changing the color of the form objects
         ChangeObjectsColor();
     }
  }

The form must be blocked at the moment when the context menu becomes visible, that is in the CContextMenu::Show() method.

//+------------------------------------------------------------------+
//| Shows a context menu                                             |
//+------------------------------------------------------------------+
void CContextMenu::Show(void)
  {
//--- Leave, if the element is already visible
//--- Show the objects of the context menu
//--- Show menu items
//--- Assign the status of a visible element
//--- State of the context menu
//--- Register the state in the previous node
//--- Block the form
   m_wnd.IsLocked(true);
  }

It may seem that calling the CWindow::IsLocked() method in the CContextMenu::Hide() method is sufficient for unblocking. This option is not suitable as several context menus can be open at the same time. Not all of them are closed simultaneously. Let us recall in what cases open context menus are closed altogether. Some conditions have to be met for that. For instance, in the CContextMenu::CheckHideContextMenus() method, when after the check of all conditions, a signal for closing all context menus is sent. The second case, in the CContextMenu::ReceiveMessageFromMenuItem() method when the ON_CLICK_MENU_ITEM event is being handled. 

We are going to add unblocking of the form to these methods. Below are the shortened versions of the methods. The comments will help you to identify where the code highlighted in yellow has to be added.

//+------------------------------------------------------------------+
//| Condition check for closing all context menus                    |
//+------------------------------------------------------------------+
void CContextMenu::CheckHideContextMenus(void)
  {
//--- Leave, if the cursor is in the context menu area or in the previous node area
//--- If the cursor is outside of the area of these elements, then ...
//    ... a check is required if there are open context menus which were activated after that
//--- For that iterate over the list of this context menu ...
//    ... for identification if there is a menu item containing a context menu
//--- Unblock the form
   m_wnd.IsLocked(false);
//--- Send a signal for hiding all context menus
  }
//+------------------------------------------------------------------+
//| Receiving a message from the menu item for handling              |
//+------------------------------------------------------------------+
void CContextMenu::ReceiveMessageFromMenuItem(const int id_item,const int index_item,const string message_item)
  {
//--- If there is an indication that the message was received from this program and the element id matches
//--- Hide the context menu
//--- Unblock the form
   m_wnd.IsLocked(false);
  }

If at this stage all files are compiled and the EA we tested earlier is loaded on to the chart, we will see immediately after the context menu is open that everything is working quite differently to what we expected. All menu items, even the ones that are in the activated context menu, will be prevented from changing color. This should not be happening. In such cases, a context menu should have its own method for changing the color of its items. Let us create such a method in the CContextMenu class and locate its call in the timer as shown in the code below.

class CContextMenu : public CElement
  {
public:
   //--- Changes the color of menu items when the cursor is hovering over them
   void              ChangeObjectsColor(void);
  };
//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CContextMenu::OnEventTimer(void)
  {
//--- Changing the color of menu items when the cursor is hovering over them
   ChangeObjectsColor();
  }
//+------------------------------------------------------------------+
//| Changing the object color when the cursor is hovering over it    |
//+------------------------------------------------------------------+
void CContextMenu::ChangeObjectsColor(void)
  {
//--- Leave, if the context menu is disabled
   if(!m_context_menu_state)
      return;
//--- Iterate over all menu items
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Change the color of the menu item
      m_items[i].ChangeObjectsColor();
     }
  }

Now, everything should be working as designed.

Fig. 3. Test of blocking the form and all controls except the currently activated one.

Fig. 3. Test of blocking the form and all controls except the currently activated one.

 

 


Methods for Communication with the Main Menu

We are going to continue developing the CMenuBar class for creating the main menu. The remaining part concerns managing this element. Let us consider in detail how the main menu works with the examples of other programs. After the program is loaded, the main menu is deactivated by default. In this state, when the mouse cursor is hovering over the main menu items, they are just highlighted. When one of the item is clicked on, the main menu is activated and the context menu appears from the item that was clicked on. When the main menu is activated this way, if the mouse cursor is moving along the menu items, the context menus will be automatically switching depending on what item the mouse cursor is hovering over.

Before we implement such a behavior in our library, let us create three context menus and attach them to the items of the main menu. To save space, we will consider a shortened version of one of them. You can see complete files of the EA at the end of the article. 

The code below contains only significant lines of code. Please note how the pointer to the previous node is passed to the context menu and the context menu pointer is stored in the main menu. The calculation of the coordinates from the lower part of the item must be set up when the properties of the context menus for the main menu are set up. In every other respect, the creation of the context menu is not different from what was considered earlier.

//+------------------------------------------------------------------+
//| Creates a context menu                                           |
//+------------------------------------------------------------------+
bool CProgram::CreateMBContextMenu1(void)
  {
//--- Three items in the context menu
//--- Store the window pointer
   m_mb_contextmenu1.WindowPointer(m_window);
//--- Store the pointer to the previous node
   m_mb_contextmenu1.PrevNodePointer(m_menubar.ItemPointerByIndex(0));
//--- Attach the context menu to the specified menu item
   m_menubar.AddContextMenuPointer(0,m_mb_contextmenu1);
//--- Array of item names
//--- Label array for the available mode
//--- Label array for the blocked mode
//--- Array of item types
//--- Set up properties before creation
   m_mb_contextmenu1.FixSide(FIX_BOTTOM);
//--- Add items to the context menu
//--- Separation line after the second item
//--- Deactivate the second item
//--- Create a context menu
   if(!m_mb_contextmenu1.CreateContextMenu(m_chart_id,m_subwin))
      return(false);
//--- Add the object to the common array of the object groups
   CWndContainer::AddToElementsArray(0,m_mb_contextmenu1);
   return(true);
  }

Now, let us set up the event handlers in the СMenuBar class of the main menu. We are going to start with handling of clicking on the menu items. Before that, similar to the CMenuItem and CContextMenu classes, we will require the OnClickMenuItem() method and methods designated to extracting the index and menu item identifier from the name of the object that was clicked on. 

The methods for identification are the same as in the CContextMenu class. However, handling of clicking on the menu item has its peculiarities in the СMenuBar class. Check of identifier is followed by a check for correctness of the context menu pointer by the index obtained from the object name. If there is no pointer, a signal for closing all open context menus is sent. If there is a pointer, then a signal for closing all context menus is sent only in the case if clicking on the item was for closing the current context menu. 

class CMenuBar : public CElement
  {
private:
   //--- Handling clicking on the menu item
   bool              OnClickMenuItem(const string clicked_object);
   //--- Getting (1) the identifier and (2) index from the menu item name
   int               IdFromObjectName(const string object_name);
   int               IndexFromObjectName(const string object_name);
   //---
  };
//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CMenuBar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handling of the left mouse click event on the main menu item
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      if(OnClickMenuItem(sparam))
         return;
     }
  }
//+------------------------------------------------------------------+
//| Clicking on the main menu item                                   |
//+------------------------------------------------------------------+
bool CMenuBar::OnClickMenuItem(const string clicked_object)
  {
//--- Leave, if the clicking was not on the menu item
   if(::StringFind(clicked_object,CElement::ProgramName()+"_menuitem_",0)<0)
      return(false);
//--- Get the identifier and the index from the object name
   int id    =IdFromObjectName(clicked_object);
   int index =IndexFromObjectName(clicked_object);
//--- Leave, if the identifier does not match
   if(id!=CElement::Id())
      return(false);
//--- If there is a context menu pointer
   if(CheckPointer(m_contextmenus[index])!=POINTER_INVALID)
     {
      //--- The state of the main menu depends on the visibility of the context menu
      m_menubar_state=(m_contextmenus[index].ContextMenuState())? false : true;
      //--- Set the state of the form
      m_wnd.IsLocked(m_menubar_state);
      //--- If the main menu is disabled
      if(!m_menubar_state)
         //--- Send a signal for hiding all context menus
         ::EventChartCustom(m_chart_id,ON_HIDE_CONTEXTMENUS,0,0,"");
     }
//--- If there is no context menu pointer
   else
     {
      //--- Send a signal for hiding all context menus
      ::EventChartCustom(m_chart_id,ON_HIDE_CONTEXTMENUS,0,0,"");
     }
//---
   return(true);
  }

As we can remember, handling of the ON_HIDE_CONTEXTMENUS event is carried out in the CWndEvents class. We need to add to the CWndEvents::OnHideContextMenus() method one more cycle which will enforce switching off all main menus that are present in the base.

//+------------------------------------------------------------------+
//| ON_HIDE_CONTEXTMENUS event                                       |
//+------------------------------------------------------------------+
bool CWndEvents::OnHideContextMenus(void)
  {
//--- If the signal is for hiding all context menus
   if(m_id!=CHARTEVENT_CUSTOM+ON_HIDE_CONTEXTMENUS)
      return(false);
//--- Hide all context menus
   int cm_total=CWndContainer::ContextMenusTotal(0);
   for(int i=0; i<cm_total; i++)
      m_wnd[0].m_context_menus[i].Hide();
//--- Disable main menus
   int menu_bars_total=CWndContainer::MenuBarsTotal(0);
   for(int i=0; i<menu_bars_total; i++)
      m_wnd[0].m_menu_bars[i].State(false);
//---
   return(true);
  }

If we compile all files and load the EA on to the chart, context menus will open when the main menu items are clicked on and will close after the second click.

Fig. 4. Test of calling context menus from the main menu.

Fig. 4. Test of calling context menus from the main menu.

 

Then, we are going to implement the methods that will allow us to switch context menus by moving the mouse cursor when the main menu is activated, similar to the way it is implemented in Windows applications. For that, we will need a method for highlighting the main menu items, when it is activated and an auxiliary method for defining an active item (the in-focus item) of activated main menu. 

class CMenuBar : public CElement
  {
public:
   //--- Changes the color when the mouse cursor is hovering over it
   void              ChangeObjectsColor(void);
   //---
private:
   //--- Returns the active item of the main menu
   int               ActiveItemIndex(void);
   //---
  };
//+------------------------------------------------------------------+
//| Changing the object color when the cursor is hovering over it    |
//+------------------------------------------------------------------+
void CMenuBar::ChangeObjectsColor(void)
  {
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
      m_items[i].ChangeObjectsColor();
  }
//+------------------------------------------------------------------+
//| Returns the index of the activated menu item                     |
//+------------------------------------------------------------------+
int CMenuBar::ActiveItemIndex(void)
  {
   int active_item_index=WRONG_VALUE;
//---
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- If the item is in focus
      if(m_items[i].MouseFocus())
        {
         //--- Store the index and stop the loop
         active_item_index=i;
         break;
        }
     }
//---
   return(active_item_index);
  }

Besides, we are going to create a method that will facilitate switching of context menus by hovering the cursor over when the main menu is activated. Let us name this method SwitchContextMenuByFocus(). The index of activated item of the main menu is to be passed to this method. This index will allow to define which context menu has to be made visible. All other context menus are hidden. At the same time, a check is carried out if there are any open context menus that are called from the main menu items. If it turns out that there are such menus, then the ON_HIDE_BACK_CONTEXTMENUS custom event is generated. We have already considered this event in detail in this article.

After the context menu has been hidden, the color of the menu item has to be reset to avoid having two items of the main menu being highlighted at the same time.

class CMenuBar : public CElement
  {
private:
   //--- Switches the context menus of the main menu by hovering the cursor over it
   void              SwitchContextMenuByFocus(const int active_item_index);
   //---
  };
//+------------------------------------------------------------------+
//| Switches context menus of the main menu by hovering cursor over  |
//+------------------------------------------------------------------+
void CMenuBar::SwitchContextMenuByFocus(const int active_item_index)
  {
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Move to the following item menu if this one does not have a context menu
      if(::CheckPointer(m_contextmenus[i])==POINTER_INVALID)
         continue;
      //--- If you make it to the specified menu item, make its context menu visible
      if(i==active_item_index)
         m_contextmenus[i].Show();
      //--- Hide the rest of context menus
      else
        {
         CContextMenu *cm=m_contextmenus[i];
         //--- Hide the context menus, which are open from other context menus.
         //    Iterate over the items of the current context menus to find out if there are any.
         int cm_items_total=cm.ItemsTotal();
         for(int c=0; c<cm_items_total; c++)
           {
            CMenuItem *mi=cm.ItemPointerByIndex(c);
            //--- Move to the following menu item if the pointer to this one is incorrect
            if(::CheckPointer(mi)==POINTER_INVALID)
               continue;
            //--- Move to the following menu item if this one does not contain a context menu
            if(mi.TypeMenuItem()!=MI_HAS_CONTEXT_MENU)
               continue;
            //--- If there is a context menu and it is activated
            if(mi.ContextMenuState())
              {
               //--- Send a signal for closing all context menus, which are open from this one
               ::EventChartCustom(m_chart_id,ON_HIDE_BACK_CONTEXTMENUS,CElement::Id(),0,"");
               break;
              }
           }
         //--- Hide the context menu of the main menu
         m_contextmenus[i].Hide();
         //--- Reset the color of the menu item
         m_items[i].ResetColors();
        }
     }
  }

Now, we only have to add newly created methods to the CHARTEVENT_MOUSE_MOVE event handler of the CMenuBar class to check the event of the mouse cursor movement:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CMenuBar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Leave, if the main menu has not been activated
      if(!m_menubar_state)
         return;
      //--- Get the index of the activated item of the main menu
      int active_item_index=ActiveItemIndex();
      if(active_item_index==WRONG_VALUE)
         return;
      //--- Change the color if the focus has changed
      ChangeObjectsColor();
      //--- Switch the context menu by the activated item of the main menu
      SwitchContextMenuByFocus(active_item_index);
      return;
     }
  }

 


Final Test of the Main Menu

Now, we can test everything that has been done in this article. Add several independent menu items to the form. Add one more internal context menu to the main menu and attach this to the second item of the third context menu as shown in the screenshot below.

Compile the files and load the EA on to the chart. To have the same result as in the screenshot below, you can load the files attached at the end of this article.

Fig. 5. General test of the main menu.

Fig. 5. General test of the main menu.


Files attached at the end of this article also contain an indicator for tests with a similar graphical interface as the EA in the above screenshot. There are also versions for tests in the MetaTrader 4 trading platform.

 

 


Conclusion

This is the last article of the second part of the series. It is vast but we have considered almost all constituent parts of the library for developing graphical interfaces. The current structure of the library can be presented as shown in the schematic below. The detailed description can be found in the last chapter of the first part.

Fig. 6. The library structure at the current stage of development.

Fig. 6. The library structure at the current stage of development.


If you made it to this point, the good news is that the most difficult part of the job has been done. In the first and second parts of this series have considered the most complex topics concerning the development of graphical interfaces. Following articles will be dedicated mainly to the creation of controls. The material will be significantly simpler and there will not be such a variety of classes.

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/2207

Attached files |
Last comments | Go to discussion (1)
Kittamet Sirinyamas
Kittamet Sirinyamas | 6 May 2020 at 08:20

Dear admin,


I tested an example file that are attached at the end of the article.

When I clicked on the menubar/mainmenu. their contextmenu didnt show completely.

Please give me some advice


Thank you.



Graphical Interfaces III: Simple and Multi-Functional Buttons (Chapter 1) Graphical Interfaces III: Simple and Multi-Functional Buttons (Chapter 1)
Let us consider the button control. We will discuss examples of several classes for creating a simple button, buttons with extended functionality (icon button and split button) and interconnected buttons (button groups and radio button). Added to that, we will introduce some additions to existing classes for controls to broaden their capability.
Thomas DeMark's contribution to technical analysis Thomas DeMark's contribution to technical analysis
The article details TD points and TD lines discovered by Thomas DeMark. Their practical implementation is revealed. In addition to that, a process of writing three indicators and two Expert Advisors using the concepts of Thomas DeMark is demonstrated.
Graphical Interfaces III: Groups of Simple and Multi-Functional Buttons (Chapter 2) Graphical Interfaces III: Groups of Simple and Multi-Functional Buttons (Chapter 2)
The first chapter of the series was about simple and multi-functional buttons. The second article will be dedicated to groups of interconnected buttons that will allow the creation of elements in an application when a user can select one of the option out of a set (group).
Universal Expert Advisor: Custom Strategies and Auxiliary Trade Classes (Part 3) Universal Expert Advisor: Custom Strategies and Auxiliary Trade Classes (Part 3)
In this article, we will continue analyzing the algorithms of the CStrategy trading engine. The third part of the series contains the detailed analysis of examples of how to develop specific trading strategies using this approach. Special attention is paid to auxiliary algorithms — Expert Advisor logging system and data access using a conventional indexer (Close[1], Open[0] etc.)