Graphical Interfaces II: the Separation Line and Context Menu Elements (Chapter 2)
Contents
- Introduction
- Developing the Class for Creating a Separation Line
- Test of Attaching a Separation Line
- Developing the Class for Creating a Context Menu
- Test of Attaching a Context Menu
- Further Development of the Class for Storing the Pointers to All Elements
- Conclusion
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.
In the previous chapter, we wrote a class for creating a menu item. This is used both as an independent control and as a part of a context and main menu. In this article, we will describe the creation of the separation line element, which also can be used not only as an independent interface element but as a part of many other elements too. After that, we will have everything required for the development of the context menu class, which will also be discussed 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.
Developing the Class for Creating a Separation Line
In a context menu, besides different types of menu items, we can often see one more interface element - a separation line. This element can be encountered not only in context menus. For instance, the status bar of the MetaTrader trading terminal and the MetaEditor code editor have vertical separation lines. That is why we will create a separate class for this object so it can be used in any other control or even used as a separate element of the graphical interface.
To give an illusion of volume, a separation line must consist of at least two parts. If one line is lighter than the background and the other one is darker, this will create a visual effect of a groove on the surface. There are two ways of creating a separation line: (1) from two primitive objects of the CRectLabel type, which are already present in the Objects.mqh file or (2) create an object of the OBJ_BITMAP_LABEL type and use it as a canvas for drawing. Let us use the second option. The standard library suggests the CCanvas class for drawing. This class already contains all necessary methods for drawing simple geometrical figures, which significantly simplifies the implementation of the intended design and will save us a lot of time.
The CCanvas class has to be embedded in a way so it can be used in a similar way to those primitive objects, which are already present in the Objects.mqh file. This can be easily achieved by making the CCanvas class derived from the CChartObjectBmpLabel class. A little change has to be introduced in the code of the CCanvas class so there are no errors or warnings later when the program is compiled. This is because in both the CCanvas class and the CChartObject class, which is the base class for the CChartObjectBmpLabel class, there is the m_chart_id field (variable). As a result, the compiler will give a warning that a variable with such a name already exists:
Fig. 1. Warning from the compiler
In practice, such a warning will not cause any critical errors and compilation will take place in spite of this. It is recommended, however, to avoid such situations because its potential impact on the work of the program is unknown. Let us assume this as a rule and follow it. Besides, the changes in the Canvas.mqh file will have to be introduced anyway because the CCanvas class has to be derived from the CChartObjectBmpLabel class. This way we can easily get rid of the persisting warning. We will simply remove the m_chart_id variable from the CCanvas class. Introducing changes in the classes of the standard library, it must be noted that at the next terminal update the files of the standard library can also be updated and the changes will get nullified. Therefore, as we cannot achieve our goal without making changes in the CCanvas class, create its copy and place it in the directory where the files of our library are located.
Create the Canvas folder in the #Include folder. Create a copy of the file containing the СCanvas class and rename it to CustomCanvas.mqh and name the class CCustomCanvas. Include the ChartObjectsBmpControls.mqh file of the standard library in the CustomCanvas.mqh file and make the CCustomCanvas class a derived one from the CChartObjectBmpLabel class. Then remove the m_chart_id variable from the body of the CCustomCanvas class and from the constructor.
//+------------------------------------------------------------------+ //| CustomCanvas.mqh | //| Copyright 2009-2013, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include <Files\FileBin.mqh> #include <Controls\Rect.mqh> #include <ChartObjects\ChartObjectsBmpControls.mqh> //... //+------------------------------------------------------------------+ //| Class CCustomCanvas | //| Usage: class for working with a dynamic resource | //+------------------------------------------------------------------+ class CCustomCanvas : public CChartObjectBmpLabel { //...
Now, include the CustomCanvas.mqh file in the Objects.mqh file:
//+------------------------------------------------------------------+ //| Objects.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Enums.mqh" #include "Defines.mqh" #include "..\Canvas\CustomCanvas.mqh" #include <ChartObjects\ChartObjectsBmpControls.mqh> #include <ChartObjects\ChartObjectsTxtControls.mqh>
Then, let us create the CRectCanvas class, which has to be a derived class from the CCustomCanvas class. The CRectCanvas class will be similar to all other classes, which are in the Objects.mqh file. Their contents were considered in the previous article. Now, it can be used for drawing any other interface elements, which will be a part of the library under development.
We have everything ready for the development of the CSeparateLine class, which is intended for the creation of a separation line. Create the SeparateLine.mqh file in the Controls folder. Include the Element.mqh and Window.mqh files in it. Then, follow the following sequence of steps:
1) create the CSeparateLine class;
2) in this class, declare a pointer to the form to which the element is going to be attached and create a method for storing a pointer in it;
3) create an instance of the CRectCanvas class;
4) declare and implement standard visual methods for all elements that can be used to manage the element.
//+------------------------------------------------------------------+ //| SeparateLine.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" //+------------------------------------------------------------------+ //| Class for creating a separation line | //+------------------------------------------------------------------+ class CSeparateLine : public CElement { private: //--- Pointer to the form to which the element is attached CWindow *m_wnd; //--- Object for creating a separation line CRectCanvas m_canvas; //--- public: CSeparateLine(void); ~CSeparateLine(void); //--- Stores the pointer to the passed form void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } //--- public: //--- Chart event handler virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- 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); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CSeparateLine::CSeparateLine(void) { //--- Store the name of the element class in the base class CElement::ClassName(CLASS_NAME); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CSeparateLine::~CSeparateLine(void) { } //+------------------------------------------------------------------+
To set up the appearance of the separation line, let us create three methods. These methods will be used for setting:
- The type of the separation line: (1) horizontal, (2) vertical.
- The color of the dark part.
- The color of the light part.
The enumeration, which will be used for specifying the type, has to be added to the Enums.mqh file:
//+------------------------------------------------------------------+ //| Enumeration of the separation line types | //+------------------------------------------------------------------+ enum ENUM_TYPE_SEP_LINE { H_SEP_LINE =0, V_SEP_LINE =1 };
Now, we can add the corresponding variables and methods to the CSeparateLine class and carry out the initialization by default values in the constructor:
class CSeparateLine : public CElement { private: //--- Properties ENUM_TYPE_SEP_LINE m_type_sep_line; color m_dark_color; color m_light_color; //--- public: //--- (1) Line type, (2) line colors void TypeSepLine(const ENUM_TYPE_SEP_LINE type) { m_type_sep_line=type; } void DarkColor(const color clr) { m_dark_color=clr; } void LightColor(const color clr) { m_light_color=clr; } //--- }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CSeparateLine::CSeparateLine(void) : m_type_sep_line(H_SEP_LINE), m_dark_color(clrBlack), m_light_color(clrDimGray) { }
We only have to add the methods for creating the element and the method, in which a separation line will be drawn on the canvas. The following parameters must be passed to the public method for creating the element CreateSeparateLine(), which will be called in the custom application:
- chart identifier;
- number of the chart window;
- number of the line index. This is necessary for the cases when several separation lines have to be created in a loop either in a context menu or other interface elements. In this case, the only element identifier is not sufficient for creating a unique name of the graphical object;
- coordinates;
- size.
class CSeparateLine : public CElement { public: //--- Creating a separation line bool CreateSeparateLine(const long chart_id,const int subwin,const int index, const int x,const int y,const int x_size,const int y_size); //--- private: //--- Creates the canvas for drawing a separation line bool CreateSepLine(void); //--- Drawing a separation line void DrawSeparateLine(void); //--- };
The code of the CreateSeparateLine() method does not differ from other similar methods in principle (for instance, in the CMenuItem class), that is why we will next consider the code of the CreateSepLine() method.
Similar to all other methods of this type, the name of the graphical object is set at the beginning. Then the graphical object (canvas) is created on which we will draw. It should be noted that for the creation of the object of the OBJ_BITMAP_LABEL type, the CreateBitmapLabel() method is used, which belongs to the CCustomCanvas class. In this class, when objects are created, attaching the object to the chart is not provided as in the CChartObjectBmpLabel class, where the CChartObject::Attach() method of the base class is used straight after creation of the object. We ourselves must manage this. As the CCustomCanvas class was derived from the CChartObjectBmpLabel class, we can access the CChartObject::Attach() method from its base class. If the object is not attached to the chart, it will be impossible to manage.
After the object was created, attached to the chart and the properties have been set up, a separation line can be drawn on our custom canvas using the DrawSeparateLine() method. This is shown in the code below. Then the object pointer is stored in the array of the CElement base class.
//+------------------------------------------------------------------+ //| Creates the canvas for drawing a separation line | //+------------------------------------------------------------------+ bool CSeparateLine::CreateSepLine(void) { //--- Forming the object name string name=CElement::ProgramName()+"_separate_line_"+(string)CElement::Index()+"__"+(string)CElement::Id(); //--- Creating an object if(!m_canvas.CreateBitmapLabel(m_chart_id,m_subwin,name,m_x,m_y,m_x_size,m_y_size,COLOR_FORMAT_ARGB_NORMALIZE)) return(false); //--- Attaching to the chart if(!m_canvas.Attach(m_chart_id,name,m_subwin,1)) return(false); //--- Properties m_canvas.Background(false); //--- Margins from the edge point m_canvas.XGap(m_x-m_wnd.X()); m_canvas.YGap(m_y-m_wnd.Y()); //--- Draw a separation line DrawSeparateLine(); //--- Add to array CElement::AddToArray(m_canvas); return(true); }
The code of the DrawSeparateLine() method is simple. At first, get the size of the canvas. Then clear the canvas by using the CCustomCanvas::Erase() method. Then, depending on whether a horizontal or vertical line has to be drawn, the program will move to the corresponding code block. As an example, we will describe building a horizontal line. At first, coordinates for two points of the line are defined and then the first line is drawn in the upper part of the canvas. Coordinates for the second line are defined in the lower part of the canvas. If the height of the canvas is set to be two pixels, then the lines will be located very close to each other. You can arrange a margin between the upper and the lower lines by setting the height of the canvas greater than two pixels. To display the changes, the canvas must be refreshed at the very end of the method using the CCustomCanvas::Update() method.
//+------------------------------------------------------------------+ //| Draws a separation line | //+------------------------------------------------------------------+ void CSeparateLine::DrawSeparateLine(void) { //--- Coordinates for the lines int x1=0,x2=0,y1=0,y2=0; //--- Canvas size int x_size =m_canvas.X_Size()-1; int y_size =m_canvas.Y_Size()-1; //--- Clear canvas m_canvas.Erase(::ColorToARGB(clrNONE,0)); //--- If the line is horizontal if(m_type_sep_line==H_SEP_LINE) { //--- The dark line above x1=0; y1=0; x2=x_size; y2=0; //--- m_canvas.Line(x1,y1,x2,y2,::ColorToARGB(m_dark_color)); //--- The light line below x1=0; x2=x_size; y1=y_size; y2=y_size; //--- m_canvas.Line(x1,y1,x2,y2,::ColorToARGB(m_light_color)); } //--- If the line is vertical else { //--- The dark line on the left x1=0; x2=0; y1=0; y2=y_size; //--- m_canvas.Line(x1,y1,x2,y2,::ColorToARGB(m_dark_color)); //--- The light line on the right x1=x_size; y1=0; x2=x_size; y2=y_size; //--- m_canvas.Line(x1,y1,x2,y2,::ColorToARGB(m_light_color)); } //--- Refreshing canvas m_canvas.Update(); }
Test of Attaching a Separation Line
Now, we can test how this works. In the previous article, we attached a menu item to the form. Following the same principle, a separation line can be attached as a separate interface element.
I will briefly remind you about the process of attaching an element to the form.
- If the element class is not in the base yet, its file has to be included in the WndContainer.mqh file.
- As the custom class of the application (in our case this is CProgram) must be derived from CWndContainer -> CWndEvents, then the creation of the element class instance and the method for it will become available after including the element file.
- Then, call the method for creating the element in the method where the graphical interface of the program is created.
If everything is done correctly, then after the program compilation and loading it on to the chart, the result should be as follows:
Fig. 2. Test of the separation line element.
The development of the CSeparateLine class is finished, and we have everything ready to start implementing the class for creating a context menu.
Developing the Class for Creating a Context Menu
Previously, in the process of the library development, three interface elements were created: (1) a form for controls (CWindow), (2) the menu item control (CMenuItem) and (3) the separation line element (CSeparateLine). Each of them belongs to a simple type of element, as they are composed only of primitive objects. A context menu is classed as a complex (compound) type of control. It will be composed not only of primitive objects but also of other elements. The base class for those elements is CElement.
Create the ContextMenu.mqh file in the Controls folder in the directory of our library. In this file include the files that will be used for creating the context menu:
//+------------------------------------------------------------------+ //| ContextMenu.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" #include "MenuItem.mqh" #include "SeparateLine.mqh"
Then, create the CContextMenu class with the standard set of virtual methods for all library elements, the form pointer and the method for storing it. We need an object of the OBJ_RECTANGLE_LABEL type as the element background. That is why to create this object, we are going to use the CRectLabel class from the Object.mqhfile. The CMenuItem class for menu items was created in the previous article. As a context menu usually contains more than one CMenuItem class and the number of these classes is not known initially, then a dynamic array of the instances of this class has to be declared. The same applies to the separation lines (CSeparateLine) of a context menu.
//+------------------------------------------------------------------+ //| Class for creating a context menu | //+------------------------------------------------------------------+ class CContextMenu : 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[]; CSeparateLine m_sep_line[]; //--- public: CContextMenu(void); ~CContextMenu(void); //--- Stores the pointer to the passed form 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); //--- Timer virtual void OnEventTimer(void); //--- Moving the element virtual void Moving(const int x,const int y); //--- (1) Show, (2) hide, (3) reset, (4) delete virtual void Show(void); virtual void Hide(void); virtual void Reset(void); virtual void Delete(void); //--- (1) Setting, (2) resetting of priorities for left clicking virtual void SetZorders(void); virtual void ResetZorders(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CContextMenu::CContextMenu(void) { //--- Store the name of the element class in the base class CElement::ClassName(CLASS_NAME); //--- Context menu is a drop-down element CElement::m_is_dropdown=true; } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CContextMenu::~CContextMenu(void) { } //+------------------------------------------------------------------+
To set up the appearance of the context menu, corresponding fields and methods are required:
class CContextMenu : public CElement { private: //--- Background properties int m_area_zorder; color m_area_color; color m_area_border_color; color m_area_color_hover; color m_area_color_array[]; //--- Properties of menu items int m_item_y_size; color m_item_back_color; color m_item_border_color; color m_item_back_color_hover; color m_item_back_color_hover_off; color m_label_color; color m_label_color_hover; string m_right_arrow_file_on; string m_right_arrow_file_off; //--- Separation line properties color m_sepline_dark_color; color m_sepline_light_color; //--- public: //--- Number of menu items int ItemsTotal(void) const { return(::ArraySize(m_items)); } //--- Methods for setting up the appearance of the context menu: // Color of the context menu background void MenuBackColor(const color clr) { m_area_color=clr; } void MenuBorderColor(const color clr) { m_area_border_color=clr; } //--- (1) Height, (2) background color and (3) color of the menu item frame void ItemYSize(const int y_size) { m_item_y_size=y_size; } void ItemBackColor(const color clr) { m_item_back_color=clr; } void ItemBorderColor(const color clr) { m_item_border_color=clr; } //--- Background color of (1) the available and (2) the blocked menu item when hovering the mouse cursor over it void ItemBackColorHover(const color clr) { m_item_back_color_hover=clr; } void ItemBackColorHoverOff(const color clr) { m_item_back_color_hover_off=clr; } //--- (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; } //--- Defining an icon for indicating the presence of a context menu in the item 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; } //--- (1) Dark and (2) light color of the separation line void SeparateLineDarkColor(const color clr) { m_sepline_dark_color=clr; } void SeparateLineLightColor(const color clr) { m_sepline_light_color=clr; } //--- };
The menu item and its context menu must be connected, otherwise it will be impossible to manage those elements correctly. To be precise, the context menu and its items must have access to the menu item to which it is attached, that is to the previous node. This means that both the CContextMenu class and the CMenuItem class must have a pointer of the CMenuItem type and methods to store and get this pointer. This pointer will also be used for checking the sequence of creating the graphical interface of the program for correctness. This will be shown later, when the methods for creating a context menu are considered.
Adding the pointer and methods for storing it and getting it in the CContextMenu class:
class CContextMenu : public CElement { private: //--- Pointer to the previous node CMenuItem *m_prev_node; //--- public: //--- Getting and storing the pointer to the previous node CMenuItem *PrevNodePointer(void) const { return(m_prev_node); } void PrevNodePointer(CMenuItem &object) { m_prev_node=::GetPointer(object); } //--- };
Same must be added to the CMenuItem class:
class CMenuItem : public CElement { private: //--- Pointer to the previous node CMenuItem *m_prev_node; //--- public: //--- Getting and storing the pointer to the previous node CMenuItem *PrevNodePointer(void) const { return(m_prev_node); } void PrevNodePointer(CMenuItem &object) { m_prev_node=::GetPointer(object); } //--- };
The graphical interface will be built in the custom class of the application (CProgram). When creating a context menu, we will require a method to specify the intended number of items in a context menu and unique values of some parameters not common for all the items. Let us write the CContextMenu::AddItem() method, which will accept as parameters: (1) the menu item text, (2) a path to the icon for the label of the available item, (3) a path to the icon for the blocked item and (4) the menu item type. Arrays to store passed values will also be required. The size of these arrays will be increased by one element each time when the CContextMenu::AddItem() method is called.
class CContextMenu : public CElement { private: //--- Arrays of the menu item properties: // (1) Text, (2) label of the available item, (3) label of the blocked item string m_label_text[]; string m_path_bmp_on[]; string m_path_bmp_off[]; //--- public: //--- Adds a menu item with specified properties before the creation of a context menu void AddItem(const string text,const string path_bmp_on,const string path_bmp_off,const ENUM_TYPE_MENU_ITEM type); //--- }; //+------------------------------------------------------------------+ //| Adds a menu item | //+------------------------------------------------------------------+ void CContextMenu::AddItem(const string text,const string path_bmp_on,const string path_bmp_off,const ENUM_TYPE_MENU_ITEM type) { //--- Increase the size of the arrays by one element int array_size=::ArraySize(m_items); ::ArrayResize(m_items,array_size+1); ::ArrayResize(m_label_text,array_size+1); ::ArrayResize(m_path_bmp_on,array_size+1); ::ArrayResize(m_path_bmp_off,array_size+1); //--- Store the values of passed parameters m_label_text[array_size] =text; m_path_bmp_on[array_size] =path_bmp_on; m_path_bmp_off[array_size] =path_bmp_off; //--- Setting the type of the menu item m_items[array_size].TypeMenuItem(type); }
To add a separation line in a context menu, we will need an array to store the index number of the menu item after which this line is to be set. The index number of the menu item will be passed to the CContextMenu::AddSeparateLine() method. The code is shown below.
class CContextMenu : public CElement { private: //--- Array of index numbers of menu items after which a separation line is to be set int m_sep_line_index[]; //--- public: //--- Adds a separation line after the specified item before the creation of a context menu void AddSeparateLine(const int item_index); //--- }; //+------------------------------------------------------------------+ //| Adds a separation line | //+------------------------------------------------------------------+ void CContextMenu::AddSeparateLine(const int item_index) { //--- Increase the size of the arrays by one element int array_size=::ArraySize(m_sep_line); ::ArrayResize(m_sep_line,array_size+1); ::ArrayResize(m_sep_line_index,array_size+1); //--- Store the index number m_sep_line_index[array_size]=item_index; }
We will need methods which will allow us to get the following, having specified the index of the menu item: (1) the pointer to the menu item, (2) description (displayed text) and (3) the type. In every method, before returning the value of the property, at first a check for exceeding the array size is carried out and then the index is adjusted. It is implemented so that if the passed index is greater that the array size, then the last item will be called and if the index is less than zero, then the first item will be called.
class CContextMenu : public CElement { public: //--- Returns the item pointer from the context menu CMenuItem *ItemPointerByIndex(const int index); //--- Returns description (displayed text) string DescriptionByIndex(const int index); //--- Returns the menu item type ENUM_TYPE_MENU_ITEM TypeMenuItemByIndex(const int index); //--- }; //+------------------------------------------------------------------+ //| Returns the menu item pointer by the index | //+------------------------------------------------------------------+ CMenuItem *CContextMenu::ItemPointerByIndex(const int index) { int array_size=::ArraySize(m_items); //--- If there is no item in the context menu, report if(array_size<1) { ::Print(__FUNCTION__," > This method is to be called, " "if the context menu has 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 item name by the index | //+------------------------------------------------------------------+ string CContextMenu::DescriptionByIndex(const int index) { int array_size=::ArraySize(m_items); //--- If there is no item in the context menu, report if(array_size<1) { ::Print(__FUNCTION__," > This method is to be called, " "if the context menu has 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 item description return(m_items[i].LabelText()); } //+------------------------------------------------------------------+ //| Returns the item type by the index | //+------------------------------------------------------------------+ ENUM_TYPE_MENU_ITEM CContextMenu::TypeMenuItemByIndex(const int index) { int array_size=::ArraySize(m_items); //--- If there is no item in the context menu, report if(array_size<1) { ::Print(__FUNCTION__," > This method is to be called, " "if the context menu has 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 item type return(m_items[i].TypeMenuItem()); }
One context menu may have several groups of radio items. To avoid confusion when identifying what item was clicked on, every radio item must have its own group identifier and the index in relation to the list of this group. The schematic below shows that besides general indices and the identifier of the context menu element, a radio item has its own distinguishing characteristics.
Fig. 3. Schematic of identifiers and indices of different groups in a context menu.
When forming a context menu, the type of the menu item has to be identified before the menu is attached to the chart. In case this is a radio item, the group it belongs to has to be specified. In other words, we need a method that will allow us to determine the identifier of each radio item. The default identifiers of radio items will be equal to zero. If we leave them like this, a context menu will have only one group of radio items no matter how many we add. There will be situations when it is required to establish the identifier of the radio item and which of them is currently highlighted. There also must be a possibility to switch radio items.
In addition to that, methods for working with checkboxes will be required. They will allow to find out the state of a checkbox and change its state if necessary. The declaration and implementation of these methods is in the code below.
class CContextMenu : public CElement { public: //--- (1) Getting and (2) setting the checkbox state bool CheckBoxStateByIndex(const int index); void CheckBoxStateByIndex(const int index,const bool state); //--- (1) Returns and (2) sets the id of the radio item by the index int RadioItemIdByIndex(const int index); void RadioItemIdByIndex(const int item_index,const int radio_id); //--- (1) Returns selected radio item, (2) switches the radio item int SelectedRadioItem(const int radio_id); void SelectedRadioItem(const int radio_index,const int radio_id); //--- }; //+------------------------------------------------------------------+ //| Returns the checkbox state by the index | //+------------------------------------------------------------------+ bool CContextMenu::CheckBoxStateByIndex(const int index) { int array_size=::ArraySize(m_items); //--- If there is no item in the context menu, report if(array_size<1) { ::Print(__FUNCTION__," > This method is to be called, " "if the context menu has 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 item state return(m_items[i].CheckBoxState()); } //+------------------------------------------------------------------+ //| Sets the checkbox state by the index | //+------------------------------------------------------------------+ void CContextMenu::CheckBoxStateByIndex(const int index,const bool state) { //--- Check for exceeding the range int size=::ArraySize(m_items); if(size<1 || index<0 || index>=size) return; //--- Set the state m_items[index].CheckBoxState(state); } //+------------------------------------------------------------------+ //| Returns the radio item id by the index | //+------------------------------------------------------------------+ int CContextMenu::RadioItemIdByIndex(const int index) { int array_size=::ArraySize(m_items); //--- If there is no item in the context menu, report if(array_size<1) { ::Print(__FUNCTION__," > This method is to be called, " "if the context menu has 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 identifier return(m_items[i].RadioButtonID()); } //+------------------------------------------------------------------+ //| Sets the radio item id by the index | //+------------------------------------------------------------------+ void CContextMenu::RadioItemIdByIndex(const int index,const int id) { //--- Check for exceeding the range int array_size=::ArraySize(m_items); if(array_size<1 || index<0 || index>=array_size) return; //--- Set the identifier m_items[index].RadioButtonID(id); } //+------------------------------------------------------------------+ //| Returns the radio item index by the id | //+------------------------------------------------------------------+ int CContextMenu::SelectedRadioItem(const int radio_id) { //--- Radio item counter int count_radio_id=0; //--- Iterate over the list of context menu items int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) { //--- Move to the following if this is not a radio item if(m_items[i].TypeMenuItem()!=MI_RADIOBUTTON) continue; //--- If identifiers match if(m_items[i].RadioButtonID()==radio_id) { //--- If this is an active radio item, leave the loop if(m_items[i].RadioButtonState()) break; //--- Increase the counter of radio items count_radio_id++; } } //--- Return the index return(count_radio_id); } //+------------------------------------------------------------------+ //| Switches the radio item by the index and id | //+------------------------------------------------------------------+ void CContextMenu::SelectedRadioItem(const int radio_index,const int radio_id) { //--- Radio item counter int count_radio_id=0; //--- Iterate over the list of context menu items int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) { //--- Move to the following if this is not a radio item if(m_items[i].TypeMenuItem()!=MI_RADIOBUTTON) continue; //--- If identifiers match if(m_items[i].RadioButtonID()==radio_id) { //--- Switch the radio item if(count_radio_id==radio_index) m_items[i].RadioButtonState(true); else m_items[i].RadioButtonState(false); //--- Increase the counter of radio items count_radio_id++; } } }
Everything is ready for creating methods for attaching a context menu to the chart. The attachment will have three stages:
- Creating the context menu background.
- Creating menu items.
- Creating separation lines.
Every stage requires a private method. Later, they will be called in the common public method. Declare them in the class body:
class CContextMenu : public CElement { public: //--- Methods for creating a context menu bool CreateContextMenu(const long chart_id,const int window,const int x=0,const int y=0); //--- private: bool CreateArea(void); bool CreateItems(void); bool CreateSeparateLine(const int line_number,const int x,const int y); //--- };
The height of the context menu background depends on the number of items and separation lines. That is why there is no point in setting this value in the application class as this value will be replaced in the CContextMenu::CreateArea() method which is designed for setting the context menu background. The height for the separation line area will be equal to nine pixels and, therefore, the number of lines must be multiplied by this value to calculate the area they occupy.
//+------------------------------------------------------------------+ //| Creates the common area of the context menu | //+------------------------------------------------------------------+ bool CContextMenu::CreateArea(void) { //--- Forming the object name string name=CElement::ProgramName()+"_contextmenu_bg_"+(string)CElement::Id(); //--- Calculation of the context menu height depends on the number of menu items and separation lines int items_total =ItemsTotal(); int sep_y_size =::ArraySize(m_sep_line)*9; m_y_size =(m_item_y_size*items_total+2)+sep_y_size-(items_total-1); //--- Set up the context menu background if(!m_area.Create(m_chart_id,name,m_subwin,m_x,m_y,m_x_size,m_y_size)) return(false); //--- Set properties m_area.BackColor(m_area_color); m_area.Color(m_area_border_color); m_area.BorderType(BORDER_FLAT); m_area.Corner(m_corner); m_area.Selectable(false); m_area.Z_Order(m_area_zorder); m_area.Tooltip("\n"); //--- Margins from the edge point m_area.XGap(m_x-m_wnd.X()); m_area.YGap(m_y-m_wnd.Y()); //--- Размеры фона m_area.XSize(m_x_size); m_area.YSize(m_y_size); //--- Set the object pointer CElement::AddToArray(m_area); return(true); }
Separation lines will be set by the CContextMenu::CreateSeparateLine() method. The line number and coordinates are to be passed to this method as parameters:
//+------------------------------------------------------------------+ //| Creates a separation line | //+------------------------------------------------------------------+ bool CContextMenu::CreateSeparateLine(const int line_number,const int x,const int y) { //--- Store the form pointer m_sep_line[line_number].WindowPointer(m_wnd); //--- Set properties m_sep_line[line_number].TypeSepLine(H_SEP_LINE); m_sep_line[line_number].DarkColor(m_sepline_dark_color); m_sep_line[line_number].LightColor(m_sepline_light_color); //--- Creating a separation line if(!m_sep_line[line_number].CreateSeparateLine(m_chart_id,m_subwin,line_number,x,y,m_x_size-10,2)) return(false); //--- Set the object pointer CElement::AddToArray(m_sep_line[line_number].Object(0)); return(true); }
The CContextMenu::CreateSeparateLine() method will be called in the CContextMenu::CreateItems() method for setting menu items. Coordinates and the sequence of setting these element will be defined in one loop. Earlier, we have considered the m_sep_line_index[] array. The index numbers of the menu items after which a separation line is to be set will be stored in this array when a context menu is formed. Comparing the number of the current iteration of the loop and index numbers of the menu items in the m_sep_line_index[] array can identify after which point the separation line is to be set.
It is also necessary to store the pointer to the previous node before setting each item in the context menu. The code of the CContextMenu::CreateItems() method with detailed comments is presented below.
//+------------------------------------------------------------------+ //| Creates a list of menu items | //+------------------------------------------------------------------+ bool CContextMenu::CreateItems(void) { int s =0; // For identification of the location of separation lines int x =m_x+1; // X coordinate int y =m_y+1; // Y coordinate. Will be calculated in a loop for each menu item. //--- Number of separation lines int sep_lines_total=::ArraySize(m_sep_line_index); //--- int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) { //--- Calculation of the Y coordinate y=(i>0)? y+m_item_y_size-1 : y; //--- Store the form pointer m_items[i].WindowPointer(m_wnd); //--- Add the pointer to the previous node m_items[i].PrevNodePointer(m_prev_node); //--- Set properties m_items[i].XSize(m_x_size-2); m_items[i].YSize(m_item_y_size); m_items[i].IconFileOn(m_path_bmp_on[i]); m_items[i].IconFileOff(m_path_bmp_off[i]); m_items[i].AreaBackColor(m_area_color); m_items[i].AreaBackColorOff(m_item_back_color_hover_off); m_items[i].AreaBorderColor(m_area_color); m_items[i].LabelColor(m_label_color); m_items[i].LabelColorHover(m_label_color_hover); m_items[i].RightArrowFileOn(m_right_arrow_file_on); m_items[i].RightArrowFileOff(m_right_arrow_file_off); m_items[i].IsDropdown(m_is_dropdown); //--- Margins from the edge point of the panel m_items[i].XGap(x-m_wnd.X()); m_items[i].YGap(y-m_wnd.Y()); //--- Creating a menu item if(!m_items[i].CreateMenuItem(m_chart_id,m_subwin,i,m_label_text[i],x,y)) return(false); //--- Move to the next one if all separation lines are set up if(s>=sep_lines_total) continue; //--- If all indices match, then a separation line can be set up after this item if(i==m_sep_line_index[s]) { //--- Coordinates int l_x=x+5; y=y+m_item_y_size+2; //--- Setting up a separation line if(!CreateSeparateLine(s,l_x,y)) return(false); //--- Adjustment of the Y coordinate for the following item y=y-m_item_y_size+7; //--- Increase the counter for separation lines s++; } } return(true); }
Then, the CContextMenu::CreateContextMenu() method for external use has to be implemented. At this stage, let us consider an option where a context menu must be attached to an item of the external menu or to an independent lone menu item. That means that before creating a context menu, it has to be passed a pointer to the previous node as was said earlier. Besides, the check for the presence of the form pointer, a check for the presence of the pointer to the previous node must be carried out. For the library user, this will be an additional reference point eliminating a possibility of incorrect graphical interface formation.
A context menu is usually hidden after creation as it is designed to be brought up by either clicking on another control or clicking on the working area. The Hide() method is designed for hiding objects in each element. There is a similar method in the CContextMenu class. At first objects of a context menu - background and a separation line are hidden by it. Then all menu items are hidden in a loop. At the same time, their own CMenuItem::Hide() methods are called for menu items. Separation lines could also be hidden in a similar way because this element has its own CSeparateLine::Hide() method. However, as this is only a design element, consisting of only one graphical object and is not designed for the interactions with the user, it was added to the common array of the context menu objects at the moment of creation and will be hidden in a corresponding loop.
//+------------------------------------------------------------------+ //| Hides the context menu | //+------------------------------------------------------------------+ void CContextMenu::Hide(void) { //--- Leave, if the element is hidden if(!CElement::m_is_visible) return; //--- Hide the objects of the context menu for(int i=0; i<ObjectsElementTotal(); i++) Object(i).Timeframes(OBJ_NO_PERIODS); //--- Hide menu items int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) m_items[i].Hide(); //--- Zero the focus CElement::MouseFocus(false); //--- Assign the status of a hidden control CElement::m_is_visible=false; }
All the methods for managing a context menu will be structured in a similar way and we will not consider their code here. You can see the code in the files attached to this article. We will only discuss the code of the CContextMenu::Delete() method for removing the element. Here, besides removing all graphical objects, all arrays that were used for the formation of a context menu are emptied. If this is not done, then every time when a symbol or a timeframe is changed, the list of menu items will be increased. At the stage of testing, you can try to experiment with this by just commenting on those lines.
//+------------------------------------------------------------------+ //| Deletion | //+------------------------------------------------------------------+ void CContextMenu::Delete(void) { //--- Removing objects m_area.Delete(); int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) m_items[i].Delete(); //--- Removing separation lines int sep_total=::ArraySize(m_sep_line); for(int i=0; i<sep_total; i++) m_sep_line[i].Delete(); //--- Emptying the control arrays ::ArrayFree(m_items); ::ArrayFree(m_sep_line); ::ArrayFree(m_sep_line_index); ::ArrayFree(m_label_text); ::ArrayFree(m_path_bmp_on); ::ArrayFree(m_path_bmp_off); //--- Emptying the array of the objects CElement::FreeObjectsArray(); }
Getting back to the method for creating a context menu, it must be mentioned that coordinates will be set in relation to the previous node. Let us arrange so that the library user can set custom coordinates if the necessity arises. Default coordinates will be set to zero in the CContextMenu::CreateContextMenu() method for creating a context menu. Coordinates will be calculated from the previous node automatically only unless at least one coordinate is specified. If both coordinates are specified, then an automatic calculation will be canceled.
For those context menus which are open from other context menus, their coordinates will be automatically calculated from the right part of the item to which the the menu is attached. For those context menus attached to the menu items of the main menu, coordinate calculation will be carried out from the lower part of the items. To manage this system, one more field and method for the CContextMenu class are required. Let us add a new enumeration to the Enums.mqh file:
//+------------------------------------------------------------------+ //| Enumeration of the menu attachment sides | //+------------------------------------------------------------------+ enum ENUM_FIX_CONTEXT_MENU { FIX_RIGHT =0, FIX_BOTTOM =1 };
A corresponding field and the method for setting the coordinate calculation mode must be added to the class of the context menu. By default, the coordinates will be calculated from the right part of the item.
class CContextMenu : public CElement { private: //--- Attachment side of the context menu ENUM_FIX_CONTEXT_MENU m_fix_side; //--- public: //--- Setting the context menu attachment mode void FixSide(const ENUM_FIX_CONTEXT_MENU side) { m_fix_side=side; } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CContextMenu::CContextMenu(void) : m_fix_side(FIX_RIGHT) { }
Below is the code of the CContextMenu::CreateContextMenu() method. Creating an element is possible only if there is a pointer to it. Properties of this node will be available only after a corresponding check discussed earlier has been completed and this will allow to calculate relative coordinates automatically. Hiding a context menu in the code must be located after its creation.
//+------------------------------------------------------------------+ //| Creates a context menu | //+------------------------------------------------------------------+ bool CContextMenu::CreateContextMenu(const long chart_id,const int subwin,const int x=0,const int y=0) { //--- Leave, if there is no form pointer if(::CheckPointer(m_wnd)==POINTER_INVALID) { ::Print(__FUNCTION__," > Before creating a context menu it has to be passed " "a window object using the WindowPointer(CWindow &object)."); return(false); } //--- Leave, if there is no pointer to the previous node if(::CheckPointer(m_prev_node)==POINTER_INVALID) { ::Print(__FUNCTION__," > Before creating a context menu it has to be passed " "the pointer to the previous node using the CContextMenu::PrevNodePointer(CMenuItem &object) method."); return(false); } //--- Initialization of variables m_id =m_wnd.LastId()+1; m_chart_id =chart_id; m_subwin =subwin; //--- If coordinates are not specified if(x==0 || y==0) { m_x =(m_fix_side==FIX_RIGHT)? m_prev_node.X2()-3 : m_prev_node.X()+1; m_y =(m_fix_side==FIX_RIGHT)? m_prev_node.Y()-1 : m_prev_node.Y2()-1; } //--- If coordinates are specified else { 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 the context menu if(!CreateArea()) return(false); if(!CreateItems()) return(false); //--- Hide element Hide(); return(true); }
A check for presence of the previous node pointer with a condition has to be added to the CMenuItem class of the CreateMenuItem() method. Absence of a pointer will mean that an independent menu item is implied. This means that this item is not a part of a context menu. Such items can be either items of a simple type (MI_SIMPLE) or items containing a context menu (MI_HAS_CONTEXT_MENU). It might be difficult to understand this right now but it will become clear when we look at examples at the end of this article.
Place this code to the CMenuItem::CreateMenuItem() method after checking for presence of the form pointer as shown below.
//--- If there is no pointer to the previous node, then // an independent menu item is implied, that is the one that is not a part of the context menu if(::CheckPointer(m_prev_node)==POINTER_INVALID) { //--- Leave, if the set type does not match if(m_type_menu_item!=MI_SIMPLE && m_type_menu_item!=MI_HAS_CONTEXT_MENU) { ::Print(__FUNCTION__," > The type of the independent menu item can be only MI_SIMPLE or MI_HAS_CONTEXT_MENU,", "that is only with a context menu.\n", __FUNCTION__," > The type of the menu item can be set using the CMenuItem::TypeMenuItem() method"); return(false); } }
Test of Attaching a Context Menu
Attaching a context menu to the chart can be tested right now. Include the ContextMenu.mqh file with the CContextMenu class of the menu to the library as shown below.
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Window.mqh" #include "MenuItem.mqh" #include "ContextMenu.mqh" #include "SeparateLine.mqh"
Create an instance of the CContextMenu class in the custom class of the CProgram application and declare the method for creating a context menu. Margins from the edge point of the form do not have to be specified as they will be calculated in relation to the menu item to which they are attached.
class CProgram : public CWndEvents { private: //--- Menu item and context menu CMenuItem m_menu_item1; CContextMenu m_mi1_contextmenu1; //--- private: #define MENU_ITEM1_GAP_X (6) #define MENU_ITEM1_GAP_Y (25) bool CreateMenuItem1(const string item_text); bool CreateMI1ContextMenu1(void); };
Now, let us form the context menu. It will have five items: three simple ones (MI_SIMPLE) and two items of the checkbox type (MI_CHECKBOX). Include the resources with icons for their labels for simple items outside of the method body. The icon will be colored for the available item and colorless for the blocked one. You can download them by the links at the end of this article. Then, at the very beginning of the method, store the pointers to the form and the previous node in the context menu. Without these actions, the graphical interface will not be created and the program will be removed from the chart. Then follow the arrays with (1) the item description (displayed text), with the icons of (2) the available and (3) blocked states (4) and item types. After that, we need to specify common properties of all items and then add them to the class of the context menu in a loop using the CContextMenu::AddItem() method. Add a separation line after the second item (index 1). After all these actions have been completed, the context menu can be attached to the chart. At the very end of the method, add the element pointer to the base. Below is the code of the method.
//+------------------------------------------------------------------+ //| Creates a context menu | //+------------------------------------------------------------------+ #resource "\\Images\\Controls\\coins.bmp" #resource "\\Images\\Controls\\coins_colorless.bmp" #resource "\\Images\\Controls\\line_chart.bmp" #resource "\\Images\\Controls\\line_chart_colorless.bmp" #resource "\\Images\\Controls\\safe.bmp" #resource "\\Images\\Controls\\safe_colorless.bmp" //--- bool CProgram::CreateMI1ContextMenu1(void) { //--- Five items in the context menu #define CONTEXTMENU_ITEMS1 5 //--- Store the window pointer m_mi1_contextmenu1.WindowPointer(m_window); //--- Store the pointer to the previous node m_mi1_contextmenu1.PrevNodePointer(m_menu_item1); //--- Array of item names string items_text[CONTEXTMENU_ITEMS1]= { "ContextMenu 1 Item 1", "ContextMenu 1 Item 2", "ContextMenu 1 Item 3", "ContextMenu 1 Item 4", "ContextMenu 1 Item 5" }; //--- Label array for the available mode string items_bmp_on[CONTEXTMENU_ITEMS1]= { "Images\\Controls\\coins.bmp", "Images\\Controls\\line_chart.bmp", "Images\\Controls\\safe.bmp", "","" }; //--- Label array for the blocked mode string items_bmp_off[CONTEXTMENU_ITEMS1]= { "Images\\Controls\\coins_colorless.bmp", "Images\\Controls\\line_chart_colorless.bmp", "Images\\Controls\\safe_colorless.bmp", "","" }; //--- Array of item types ENUM_TYPE_MENU_ITEM items_type[CONTEXTMENU_ITEMS1]= { MI_SIMPLE, MI_SIMPLE, MI_SIMPLE, MI_CHECKBOX, MI_CHECKBOX }; //--- Set up properties before creation m_mi1_contextmenu1.XSize(160); m_mi1_contextmenu1.ItemYSize(24); m_mi1_contextmenu1.AreaBackColor(C'240,240,240'); m_mi1_contextmenu1.AreaBorderColor(clrSilver); m_mi1_contextmenu1.ItemBackColorHover(C'240,240,240'); m_mi1_contextmenu1.ItemBackColorHoverOff(clrLightGray); m_mi1_contextmenu1.ItemBorderColor(C'240,240,240'); m_mi1_contextmenu1.LabelColor(clrBlack); m_mi1_contextmenu1.LabelColorHover(clrWhite); m_mi1_contextmenu1.RightArrowFileOff("Images\\EasyAndFastGUI\\Controls\\RightTransp_black.bmp"); m_mi1_contextmenu1.SeparateLineDarkColor(C'160,160,160'); m_mi1_contextmenu1.SeparateLineLightColor(clrWhite); //--- Add items to the context menu for(int i=0; i<CONTEXTMENU_ITEMS1; i++) m_mi1_contextmenu1.AddItem(items_text[i],items_bmp_on[i],items_bmp_off[i],items_type[i]); //--- Separation line after the second item m_mi1_contextmenu1.AddSeparateLine(1); //--- Create the context menu if(!m_mi1_contextmenu1.CreateContextMenu(m_chart_id,m_subwin)) return(false); //--- Add the control pointer to the base CWndContainer::AddToElementsArray(0,m_mi1_contextmenu1); return(true); }
Now, add the call of the method for creating a context menu to the main method for creating the graphical interface. Although the context menu is to be hidden at the set up, it will be shown in this test. The code below shows what lines have to be added to the CProgram::CreateTradePanel() method.
//+------------------------------------------------------------------+ //| Creates a 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); if(!CreateMI1ContextMenu1()) return(false); //--- Separation line if(!CreateSepLine()) return(false); //--- Show the context menu m_mi1_contextmenu1.Show(); //--- Redrawing of the chart m_chart.Redraw(); return(true); }
Compile the files and load the program on to the chart. The result should be as in the screenshot below
Fig. 4. Test of the context menu element.
At this stage, when the mouse cursor is hovering over the items of the context menu, they will not change their color. This functionality can be created in the CContextMenu class of the context menu or you can use a ready-made one in the CMenuItem class of the menu item. After the context menu has been attached to the chart, its pointer is added to the base. However, the pointers to each menu item stay unavailable for use in the CWndEvents class for event handling in the current implementation of adding pointers to the common array for elements. For each complex (compound) control, which consists of several elements, we will create in the CWndContainer class a method in which the pointers to these elements can be obtained. For that, we implemented the ItemPointerByIndex() method in the CContextMenu class, using which we can get the pointer to the menu item by specifying its index.
Further Development of the Class for Storing the Pointers to All Elements
Let us implement the AddContextMenuElements() method in the CWndContainer class for work with the context menu element. The form index and the element object are to be passed to it as parameters. At the beginning of the method, a check will be carried out if the passed element is a context menu. If it is, then a pointer to the context menu (CContextMenu) is required to get access to its methods. How can this be done if the object that is being passed belongs to the base class (CElement)? For that, it is sufficient to declare a pointer with the CContextMenu type and assign the pointer to the passed object to it. In the code below it is highlighted in yellow. This way, there is an access to the items of the context menu. Then, they are added in a loop to the common array of elements of their form. At the end of each iteration, menu items are passed to the CWndContainer::AddToObjectsArray() method for storing pointers to the objects constituting them in the object array of the CChartObject type.
//+------------------------------------------------------------------+ //| Class for storing all interface objects | //+------------------------------------------------------------------+ class CWndContainer { protected: //--- Stores pointers to the context menu elements in the base bool AddContextMenuElements(const int window_index,CElement &object); }; //+------------------------------------------------------------------+ //| Stores pointers to the context menu elements in the base | //+------------------------------------------------------------------+ bool CWndContainer::AddContextMenuElements(const int window_index,CElement &object) { //--- Leave, if this is not a context menu if(object.ClassName()!="CContextMenu") return(false); //--- Get the context menu pointers CContextMenu *cm=::GetPointer(object); //--- Store the pointers to its objects in the base int items_total=cm.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 pointers CMenuItem *mi=cm.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); } //--- return(true); }
This will be called in the CWndContainer::AddToElementsArray() method straight after increasing the element counter. For the sake of saving space in the article, we show only a shortened version of the code. The full version can be found in the files attached to this article.
//+------------------------------------------------------------------+ //| 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 if(AddContextMenuElements(window_index,object)) return; }
The code of the CWndContainer::AddToElementsArray() method will be enriched in the same way with other similar methods for complex (compound) elements.
If we compile all the files and load the program on to the chart, the items of the context menu will change their appearance when the mouse cursor is hovering over them.
Fig. 5. Test of the context menu items.
Development of the class for creating a context menu is completed. The next step is to set up its event handler and the event handler of menu items. We will deal with this in the next article.
Conclusion
Our library already contains three classes for creating such elements as:
- a menu item;
- a separation line;
- a context menu.
In the next article, we will set up event handlers of the library in the main class and in the classes of controls created earlier.
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:
- Graphical Interfaces II: the Menu Item Element (Chapter 1)
- Graphical Interfaces II: the Separation Line and Context Menu Elements (Chapter 2)
- Graphical Interfaces II: Setting Up the Event Handlers of the Library (Chapter 3)
- Graphical Interfaces II: The Main Menu Element (Chapter 4)
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/2202
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use