
Graphical Interfaces I: Preparation of the Library Structure (Chapter 1)
Contents
- Introduction
- List of Controls
- Base Classes of the Standard Library as Primitive Objects
- Derived Classes of Primitive Objects with Additional Methods
- The Base Class for all Controls
- Base Classes for an Application with a Graphical Interface
- Test of Event Handlers of the Library and Program Classes
- Conclusion
Introduction
This article is the beginning of another series concerning development of graphical interfaces. Currently, there is not a single code library that would allow quick and easy creation of high quality graphical interfaces within MQL applications. By that, I mean the graphical interfaces that we are used to in familiar operating systems.
The goal of this project is to provide the end user with such an opportunity and show how to use it with the help of this library. I aimed to make it as simple to study as possible and left a space for further development.
It is worth mentioning the code library by Dmitry Fedoseev shared by him in the series of his articles:
- Custom Graphical Controls. Part 1. Creating a Simple Control
- Custom Graphical Controls. Part 2. Creating a Simple Control
- Custom Graphical Controls. Part 3. Forms
Although both the library suggested by Dmitry and the standard library have their advantages, there are also some disadvantages. Dmitry has provided a detailed description in the form of an instruction manual and a broader range of user interface elements than in the standard library. However, I do not agree with some parts of the functionality and the implemented control mechanism. Some forum members shared their interesting ideas about that in their comments and I took some of them into consideration.
The standard library does not feature a detailed description, and, in my opinion, its structure is architected better but still not thought through well enough. Adding to that, the standard library does not contain many controls that may be required for the creation of graphical interfaces. In both implementations, when a question about modernization and improving quality arises, one cannot help but think that they have to be built from scratch. In my articles, I made an attempt to gather all the advantages and eliminate all the disadvantages.
Earlier, I wrote two simple introductory articles concerning controls:
- MQL5 Cookbook: Indicator Subwindow Controls - Buttons
- MQL5 Cookbook: Indicator Subwindow Controls - Scrollbar
These are written in a procedural style and serve the purpose of getting to know MQL. Now, it is time to show a more complex structure on the example of a quite large project, which will be implemented in the object-oriented form.
What will the reader gain after reading these articles?
- The aim of this project is to create ultimately intuitive interfaces for the end user. Developers of those user interfaces will be provided with an easy to study and use code library, which can be developed further.
- Those developers, who are just starting to make the first steps in implementing large scale projects using object oriented methods or starting to learn object-oriented programming, will find specific material for studying with a lot of examples from the beginning to the implementation.
- More experienced developers will find one more implementation of a library for graphical user interfaces so they can start to realize their ideas. It is good when there is a choice.
- Those professionals who can create such libraries and are likely to have similar libraries can criticize and challenge the suggested implementation. They may be able to suggest a more suitable and efficient approach to the implementation of such projects, which is going to be interesting for less experienced readers. Such discussions are sometimes as interesting as the article itself.
I called the narration method used in this series of articles "an attempted imitation of ideal sequence". Often, in the process of real life large scale project development, sequential order of actions and the line of thinking are more chaotic and consist of many experiments, trial and error. Here, we will skip all those complications. For those who come across projects of this scale for the first time, it is recommended to repeat all the actions for a better understanding of the material, when studying this library, and the process of its development. The articles of this series give an opportunity to present the train of thought in the ideal sequence when all the answers to the majority of questions are already present and all parts of the project are created as required.
List of Controls
So, what is this library supposed to be like? What should the object-oriented programming structure of the library of this code be like? What shall we start with? In fact, there are quite a lot of questions ahead. Let us define what controls and interface elements are necessary for creating a convenient MQL application. Everyone has their own requirements and the scale of ideas varies from person to person. For one person, a couple of buttons and checkboxes are enough. Others have in mind multi-window interfaces, featuring different kinds of data set selection and controls.
I would like to mention that the implementation described in this series of articles can be used as an end product in the trading platforms MetaTrader 4 and MetaTrader 5 straight after publication. If we compare it with some ideal, then of course, it will be clear that there is space for growth. After the articles from this series have been published, I will share my thoughts on what I think an ideal implementation of a library for creating graphical interfaces in MQL should be.
The initial version of the library will contain interface elements from the list below. Some of them will be implemented in several ways and presented as separate code classes, where each of them will be designed for certain tasks. That means that some of them will be better suited for one kind of task and others for another.
- Window, to which multiple controls in any order and sequence can be added.
- Menu bar with drop-down lists.
- Context menu.
- Status bar.
- Buttons:
- Simple button.
- Icon button with extended functionality.
- Split button with several functions.
- Button groups:
- Simple button group.
- Radio button group.
- Icon button group with extended functionality.
- Checkbox.
- Spin edit.
- Checkbox with spin edit.
- Scroll:
- Vertical scroll.
- Horizontal scroll.
- List view.
- Combobox with a drop-down list view.
- Check combobox with a drop-down list view.
- Combobox with a drop-down list view and edit.
- Slider with edit:
- Single-sided slider.
- Double-sided slider.
- Calendar:
- Static calendar.
- Drop-down calendar.
- Tooltip.
- Progress bar.
- Tables:
- Label table.
- Edit table.
- Canvas table.
- Tabs.
- Tree-type or hierarchical list view.
The list presented above contains controls, which for their functioning comprise other elements from this list. For example, in the enumeration above, controls of the combobox type include the list view control, and list view in turn, includes a vertical scroll. Horizontal and vertical scrollbars are also included in all types of tables. The drop-down calendar also includes a calendar, which can be used as a separate control. Creating each element will be considered in depth after we have defined the project structure.
Base Classes of the Standard Library as Primitive Objects
We are going to use some code classes from the standard library anyway. They belong to the base graphic primitives that will be constituents for all controls. Each of these classes allows to quickly create or delete a graphical object, obtain or change any of its properties.
The class files for work with graphic primitives are located:
- MetaTrader 4: <Data folder>\MQL4\Include\ChartObjects
- MetaTrader 5: <Data folder>\MQL5\Include\ChartObjects
The article Create Your Own Market Watch Using The Standard Library Classes gives a comprehensive description and examples of using these classes, therefore we are not going to discuss them in detail. Let me just remind you that the base class of this class group is CObject. Class CChartObject is derived from it. It contains common methods applicable to all graphical objects. All other classes are derived from the CChartObject class and contain methods for managing unique properties for every single graphical object.
The common structure of interconnections of the standard library classes that belong to graphical objects can be presented as below. Let us agree that a blue arrow denotes a connection of a base class with a derived one.
Fig. 1. The common structure of standard library class interconnections
As we go along with writing a library, we will show its structure in the articles on similar diagrams. For brevity's sake, we will use a shortened version as shown below. That means that the last element in the presented diagram can be any (…) graphical object from the diagram above.
Fig. 2. Shortened version of the structure of the standard library graphical objects
To build graphical interfaces, we will need only a few of these classes:
- CChartObjectRectLabel — rectangle label.
- CChartObjectEdit — edit.
- CChartObjectLabel — text label.
- CChartObjectBmpLabel — bitmap label.
- CChartObjectButton — button.
The common property they share is that they are not attached to the time scale, that is they are not moving together with the chart when the latter is scrolled. Then, each of these primitives requires creating derived classes storing some properties that are going to be referred to often:
- Coordinates.
- Size.
- Indent from the edge of the containing element, a part of which they are going to be.
- Focus when the cursor is hovering over the object.
These classes should also contain correspondent methods for obtaining and keeping values of these properties. Realistically, the values of these properties can be obtained with the help of base classes at the level above. This approach, however, is not going to be efficient in terms of resources.
Derived Classes of Primitive Objects with Additional Methods
In the <data folder>\MQL5\Include directory (for MetaTrader 4: <data folder>\MQL4\Include) create a folder named EasyAndFastGUI. We will put all files for our library into this folder. To find a data folder, in the main menu of either MetaTrader or MetaEditor select File > Open Data Folder. In the EasyAndFastGUI folder, all the files that belong to the library for creating graphical interfaces will be stored in the Controls sub-folder. Then, in the Controls sub-folder create a file called Objects.mqh. It will contain the derived classes mentioned earlier.
In the beginning of the Objects.mqh file, include required files from the standard library:
//+------------------------------------------------------------------+ //| Objects.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include <ChartObjects\ChartObjectsBmpControls.mqh> #include <ChartObjects\ChartObjectsTxtControls.mqh>
All classes for the objects listed above will be of the same type, and therefore, I will show the code only for one of them. Actually, all of these methods can be fit in a single line. To save space in the file and to present information in the most compressed form, such methods will be located straight in the class body.
The initialization of all the variables takes place in the class constructor. A part of them is in the list of constructor initialization (before the class body). In reality, it is not important as during the development of the whole library there was not a single case when the initialization sequence of fields (variables) of derived and base classes was required. That is why in the class body all variables can be initialized or only those that require some comments.
//+------------------------------------------------------------------+ //| Class with additional properties for the object Rectangle Label | //+------------------------------------------------------------------+ class CRectLabel : public CChartObjectRectLabel { protected: int m_x; int m_y; int m_x2; int m_y2; int m_x_gap; int m_y_gap; int m_x_size; int m_y_size; bool m_mouse_focus; public: CRectLabel(void); ~CRectLabel(void); //--- Coordinates int X(void) { return(m_x); } void X(const int x) { m_x=x; } int Y(void) { return(m_y); } void Y(const int y) { m_y=y; } int X2(void) { return(m_x+m_x_size); } int Y2(void) { return(m_y+m_y_size); } //--- Indents from the edge point (xy) int XGap(void) { return(m_x_gap); } void XGap(const int x_gap) { m_x_gap=x_gap; } int YGap(void) { return(m_y_gap); } void YGap(const int y_gap) { m_y_gap=y_gap; } //--- Sizes int XSize(void) { return(m_x_size); } void XSize(const int x_size) { m_x_size=x_size; } int YSize(void) { return(m_y_size); } void YSize(const int y_size) { m_y_size=y_size; } //--- Focus bool MouseFocus(void) { return(m_mouse_focus); } void MouseFocus(const bool focus) { m_mouse_focus=focus; } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CRectLabel::CRectLabel(void) : m_x(0), m_y(0), m_x2(0), m_y2(0), m_x_gap(0), m_y_gap(0), m_x_size(0), m_y_size(0), m_mouse_focus(false) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CRectLabel::~CRectLabel(void) { }
One file will contain several classes. For quick navigation between them, in the very beginning of the file straight after the include files of the standard library, write a file content, which is essentially a list of classes without a body:
//--- List of classes in file for a quick navigation (Alt+G) class CRectLabel; class CEdit; class CLabel; class CBmpLabel; class CButton;
Now, in the similar way that you can move between functions and methods by placing the cursor in the name of the class in the list and pressing Alt+G, the required class in the file can be navigated to.
At the current stage, this scheme can be depicted as it is shown on the diagram below. Here, the rectangle with a thick blue frame is the Objects.mqh file with the classes it contains (rectangles with a thin blue frame), which have been described above. The blue frames mean that all the classes in this file are derived from one of the classes represented by the rectangle CChartObject…, from which the last blue arrow originates.
Fig. 3. Expanding structure by creating derived classes for primitive object
The question about graphical primitives is solved. Then, we need to decide how to manage those objects when they are in a group as nearly every control will consist of several simple objects. Every control is unique but they all have a common set of properties. Let us create a base class called CElement which will contain the common set of properties for every control.
The Base Class for all Controls
The CElement class will be located in the Element.mqh file, and file Objects.mqh file will be included inside it by the #include command:
//+------------------------------------------------------------------+ //| Element.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Objects.mqh" //+------------------------------------------------------------------+ //| Base control class | //+------------------------------------------------------------------+ class CElement { public: CElement(void); ~CElement(void); };
To save the space in the article, I will show the methods of this class in separate groups in the class body. At the end of the article, you can download a complete version of the class with all the methods that will be described further. We will keep this approach for all other classes.
It should be noted that none of the classes in the Objects.mqh file are a base class for the CElement class and none of them are going to be among the included objects in this class. Later, they will be used as objects in all the classes derived from the CElement class and stored in the base class only as an array of object pointers. When the Objects.mqh file is included in the Element.mqh file, we do not need to include it in the future in any other files. As a result, instead of including two files (Objects.mqh and Element.mqh), we need to include only one, that is Element.mqh.
In the Controls folder create one more file, where we will store some common properties for the whole program in the #define directives:
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ //--- Class name #define CLASS_NAME ::StringSubstr(__FUNCTION__,0,::StringFind(__FUNCTION__,"::")) //--- Program name #define PROGRAM_NAME ::MQLInfoString(MQL_PROGRAM_NAME) //--- Program type #define PROGRAM_TYPE (ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE) //--- Prevention of exceeding the array size #define PREVENTING_OUT_OF_RANGE __FUNCTION__," > Prevention of exceeding the array size." //--- Font #define FONT ("Calibri") #define FONT_SIZE (8)
Please note that before the functions in the code above there are double colons. They are not compulsory and everything is going to work correctly without them. In programming, however, it is good manners to put a double colon before the system functions of the language. This makes it unambiguous that the function is a system function.
Include the Defines.mqh file in the Objects.mqh file, so it is available for the whole chain of files included by each other (Defines.mqh -> Objects.mqh -> Element.mqh) :
//+------------------------------------------------------------------+ //| Objects.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Defines.mqh" #include <ChartObjects\ChartObjectsBmpControls.mqh> #include <ChartObjects\ChartObjectsTxtControls.mqh>
Now, we have to define what properties controls are supposed to have in common. Every control is a separate program module, which will be functioning independently from all other similar modules. However, since some controls will be gathered in groups, that is in more complex (compound) controls, there will be situations when controls will be sending messages to the main program module and other controls. That may cause the occasional necessity to define if the message was received from a control of our program as there may be several MQL applications running on the same chart.
We will also need to define:
- Control name, which will be the class name.
- Program type (EA, indicator).
- Whether the control is currently hidden.
- If this is a part of a drop-down control.
- If this is a control attached to a group of tabs.
- If the mouse cursor is over the control.
class CElement { protected: //--- (1) Name of class and (2) program, (3) program type string m_class_name; string m_program_name; ENUM_PROGRAM_TYPE m_program_type; //--- Control state bool m_is_visible; bool m_is_dropdown; int m_is_object_tabs; //--- Focus bool m_mouse_focus; //--- public: CElement(void); ~CElement(void); //--- (1) Obtaining and setting the class name, (2) obtaining the program name, // (3) obtaining program type string ClassName(void) const { return(m_class_name); } void ClassName(const string class_name) { m_class_name=class_name; } string ProgramName(void) const { return(m_program_name); } ENUM_PROGRAM_TYPE ProgramType(void) const { return(m_program_type); } //--- Control state void IsVisible(const bool flag) { m_is_visible=flag; } bool IsVisible(void) const { return(m_is_visible); } void IsDropdown(const bool flag) { m_is_dropdown=flag; } bool IsDropdown(void) const { return(m_is_dropdown); } void IsObjectTabs(const int index) { m_is_object_tabs=index; } int IsObjectTabs(void) const { return(m_is_object_tabs); } //--- Focus bool MouseFocus(void) const { return(m_mouse_focus); } void MouseFocus(const bool focus) { m_mouse_focus=focus; } };
Since there may be several identical controls in the program interface like several buttons or checkboxes, each of them should have its unique number or identifier (id). At the same time, if a control element consists of a whole array of other controls, each of them must have its index number.
class CElement { protected: //--- Identifier and index of control int m_id; int m_index; //--- public: //--- Setting and obtaining the control identifier void Id(const int id) { m_id=id; } int Id(void) const { return(m_id); } //--- Setting and obtaining the control index void Index(const int index) { m_index=index; } int Index(void) const { return(m_index); } };
As mentioned before, all objects of control graphical elements will be stored in an array of the CChartObject type as pointers to these objects. For that, we will need a method to insert object pointers, after they have been successfully created, into arrays. We also need (1) to obtain a pointer from an array having specified its index, (2) to obtain the size of the array of objects and (3) empty the array buffer.
class CElement { protected: //--- Common array of pointers to all objects in this control CChartObject *m_objects[]; //--- public: //--- Obtaining the object pointer by specified index CChartObject *Object(const int index); //--- (1) Obtaining the number of the control objects, (2) emptying the object array int ObjectsElementTotal(void) const { return(::ArraySize(m_objects)); } void FreeObjectsArray(void) { ::ArrayFree(m_objects); } //--- protected: //--- Method to add pointers of primitive objects to common array void AddToArray(CChartObject &object); }; //+------------------------------------------------------------------+ //| Returns the pointer of the control object by index | //+------------------------------------------------------------------+ CChartObject *CElement::Object(const int index) { int array_size=::ArraySize(m_objects); //--- Verifying the size of the object array if(array_size<1) { ::Print(__FUNCTION__," > No ("+m_class_name+") objects in this element!"); return(NULL); } //--- Correction in case the size was exceeded int i=(index>=array_size)? array_size-1 : (index<0)? 0 : index; //--- Return the object pointer return(m_objects[i]); } //+------------------------------------------------------------------+ //| Adds an object pointer to array | //+------------------------------------------------------------------+ void CElement::AddToArray(CChartObject &object) { int size=ObjectsElementTotal(); ::ArrayResize(m_objects,size+1); m_objects[size]=::GetPointer(object); }
Every control will have its own event handler for chart events and its own timer. In the CElement class these methods will be virtual as they cannot be made universal because every control is unique. We will discuss this matter in detail when we develop a class, which will be a container for all objects (controls). The following methods are going to be virtual too:
- Moving a control.
- Showing a control.
- Hiding a control.
- Reset. Used when it is required that all objects relevant to control are above irrelevant ones.
- Deleting all graphical control objects.
- Setting the priority values for the left click of the mouse.
- Zeroing priority values for the left click of the mouse.
class CElement { public: //--- Chart event handler virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) {} //--- Timer virtual void OnEventTimer(void) {} //--- Moving the control virtual void Moving(const int x,const int y) {} //--- (1) Showing, (2) hiding, (3) resetting, (4) deleting 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 on mouse virtual void SetZorders(void) {} virtual void ResetZorders(void) {} };
You have already seen that both in classes of graphical primitives in the Objects.mqh file and in the CElement class, there are properties and methods that will allow us to obtain the borders of the object. Consequently, there will be a chance to find out if the cursor of the mouse is in the area of control and if it is over one or the other primitive object individually. Why do we need this? This will allow us to make the graphical interface of the program highly intuitive for the user.
When the cursor is over an element of interface, the color of its background or frame will change indicating that this element of the interface can be clicked on, that is will react to a mouse click. To implement this functionality in the CElement class, we will need methods to work with color. The color array will be defined in one of them and for that only two colors will have to be passed to this method. A gradient will be calculated from them. The calculation will take place only once for each object at the moment of attaching the object to the chart. In the second method, to work with color, work will be done only with a ready color array, which will save resources significantly.
You can create a method for the gradient calculation, but we are going to use a ready-made code class, which can be downloaded from Code Base. Many methods from Code Base will be used in this project in other classes. Dmitry Fedoseev has added his version of the class for working with color (IncColors) to Code Base, but I suggest using the version that I have corrected a little. It can be downloaded at the end of the article (Colors.mqh).
This class (CColors) has a lot of methods for all occasions. The only change I introduced was the added possibility of quick navigation when the names of the methods are in the class body and the methods themselves are outside of the class body. This will allow you to find the required method more quickly and efficiently and move from the method to content and back using the Alt+G keys. This file should be located in the EasyAndFastGUI folder. We will include the file in our interface library through the Element.mqh file in the directory ..\EasyAndFastGUI\Controls. As this file will be located one level above in the directory, it must be included in the way shown below:
//+------------------------------------------------------------------+ //| Element.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Objects.mqh" #include "..\Colors.mqh"
Include the object of the CColors class to the CElement class and then (1) add a variable and method to specify the number of colors in the gradient and (2) methods for initializing the gradient array and changing the color of the specified object:
class CElement { protected: //--- Class instance for working with the color CColors m_clr; //--- Number of colors in the gradient int m_gradient_colors_total; //--- public: //--- Setting the gradient size void GradientColorsTotal(const int total) { m_gradient_colors_total=total; } //--- protected: //--- Initializing the array gradient void InitColorArray(const color outer_color,const color hover_color,color &color_array[]); //--- Changing of the object color void ChangeObjectColor(const string name,const bool mouse_focus,const ENUM_OBJECT_PROPERTY_INTEGER property, const color outer_color,const color hover_color,const color &color_array[]); };
To initialize the gradient array, use the Gradient() method from the CColors class. The following should be passed to this method as parameters: (1) array of the colors, which will be used for calculating the gradient, (2) the array that will contain the sequence of gradient colors and (3) requested size of the array, which is the supposed number of steps in the gradient.
//+------------------------------------------------------------------+ //| Initializing the gradient array | //+------------------------------------------------------------------+ void CElement::InitColorArray(const color outer_color,const color hover_color,color &color_array[]) { //--- Array of the gradient colors color colors[2]; colors[0]=outer_color; colors[1]=hover_color; //--- Formation of the color array m_clr.Gradient(colors,color_array,m_gradient_colors_total); }
In the method to change the object color there will be parameters that will allow to specify:
- The object name.
- If the mouse cursor is over the control.
- What part of the object to change the color of (for example: background, frame etc.).
- As the gradient will be formed by two colors, then these colors should be passed as two parameters for verification.
- The array of the gradient color, which is formed in the InitColorArray() function.
Please see additional comments below in the code of the ChangeObjectColor() method:
//+------------------------------------------------------------------+ //| Changing object color when hovering a cursor over the object | //+------------------------------------------------------------------+ void CElement::ChangeObjectColor(const string name,const bool mouse_focus,const ENUM_OBJECT_PROPERTY_INTEGER property, const color outer_color,const color hover_color,const color &color_array[]) { if(::ArraySize(color_array)<1) return; //--- Obtain the current object color color current_color=(color)::ObjectGetInteger(m_chart_id,name,property); //--- If the cursor is over the object if(mouse_focus) { //--- Leave, if the specified color has been reached if(current_color==hover_color) return; //--- Move from the first to the last for(int i=0; i<m_gradient_colors_total; i++) { //--- If colors do not match, move to the following if(color_array[i]!=current_color) continue; //--- color new_color=(i+1==m_gradient_colors_total)? color_array[i] : color_array[i+1]; //--- Change color ::ObjectSetInteger(m_chart_id,name,property,new_color); break; } } //--- If the cursor is not in the area of the object else { //--- Leave, if the specified color has been reached if(current_color==outer_color) return; //--- Move from the last to the first for(int i=m_gradient_colors_total-1; i>=0; i--) { //--- If colors do not match, move to the following if(color_array[i]!=current_color) continue; //--- color new_color=(i-1<0)? color_array[i] : color_array[i-1]; //--- Change color ::ObjectSetInteger(m_chart_id,name,property,new_color); break; } } }
Other common properties of all controls are the object anchor point and chart corner.
class CElement { protected: //--- Chart corner and anchor point of objects ENUM_BASE_CORNER m_corner; ENUM_ANCHOR_POINT m_anchor; }
We have completed the creation of the CElement class. At the end of the article, you can download the complete version of this class. Currently, the library structure looks as it is shown in the diagram below. Let us assume, that yellow arrows will denote that the file is included. If it contains a class though, the latter will not be a base one for those classes that are in the file where it is included. It will be used as an object included in the class the same way as it was shown above between classes CElement and CColors.
Fig. 4. Base class for the CElement controls
Base Classes for an Application with a Graphical Interface
Before we start creating interface elements, we must define how object interconnection will be implemented. The scheme should be set the way that access to each object could be established from one class, where objects are not only stored but also categorized. Thus, there is an opportunity not only to find out how many and what objects are present in this container but also manage them.
Placing all the functionality in one class is not quite convenient as the class will be crowded. Such classes (objects) are called God objects, which are anti-patterns of object-oriented programming because they have too many tasks. As the project structure grows, it will be difficult to introduce changes or additions. Therefore the objects will be stored separately from the class where events are handled. Objects will be contained in the base class CWndContainer, and the events will be handled in the CWndEvents class derived from it.
Now, let us create the CWndContainer and CWndEvents classes. As we create all the controls listed in the beginning of the article, we will be filling those classes with the required functionality. For now, we are going to determine the general structure of the project.
In the Controls folder, create files WndContainer.mqh and WndEvents.mqh. The CWndContainer class will be empty as we have not created any controls.
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Class for storing all objects of the interface | //+------------------------------------------------------------------+ class CWndContainer { protected: CWndContainer(void); ~CWndContainer(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CWndContainer::CWndContainer(void) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CWndContainer::~CWndContainer(void) { } //+------------------------------------------------------------------+
The WndContainer.mqh file must be included to the WndEvents.mqh file as the CWndEvents class will be derived from the CWndContainer class:
//+------------------------------------------------------------------+ //| WndEvents.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "WndContainer.mqh" //+------------------------------------------------------------------+ //| Class for event handling | //+------------------------------------------------------------------+ class CWndEvents : public CWndContainer { protected: CWndEvents(void); ~CWndEvents(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CWndEvents::CWndEvents(void) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CWndEvents::~CWndEvents(void) { } //+------------------------------------------------------------------+
Classes CWndContainer and CWndEvents will be the base classes for any MQL application that requires a graphical interface.
For further testing of this library during the process of its development, we will create an EA. It has to be created in a separate folder, because besides the main program file, there will be the include file Program.mqh with the class of our program (CProgram). This class will be derived from the CWndEvents class.
//+------------------------------------------------------------------+ //| Program.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include <EasyAndFastGUI\Controls\WndEvents.mqh> //+------------------------------------------------------------------+ //| Class for creation of a trading panel | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { public: CProgram(void); ~CProgram(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CProgram::CProgram(void) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CProgram::~CProgram(void) { } //+------------------------------------------------------------------+
We will need methods for event handling, which later will be called in the main program file, that is in the main function-handlers of the MQL application event:
class CProgram : public CWndEvents { public: //--- Initialization/uninitialization void OnInitEvent(void); void OnDeinitEvent(const int reason); //--- Timer void OnTimerEvent(void); //--- protected: //--- Virtual event handler of the chart virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); };
Timer and event handler of the chart must be created also at the higher level in the base class CWndEvents:
class CWndEvents : public CWndContainer { protected: //--- Timer void OnTimerEvent(void); //--- Virtual event handler of the chart virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) {} };
Please note that in the code above, both in the base class CWndEvents, and in the derived class CProgram, the OnEvent handler of the chart is declared as virtual. That said, in the CWndEvents class, this method is a dummy “{}”. This will allow us to direct the flow of events from the base class to the derived one when it is necessary. Virtual methods OnEvent() in these classes are intended for internal use. For calling from the main program file, another method of the CWndEvents class will be used. Let us name it ChartEvent(). We are also going to create helper methods for each type of main event, which will make the code more clear and readable.
Along with helper methods, which will include checking custom events, a method for checking events in controls is required. Let us name it CheckElementsEvents(). It is highlighted in green below:
class CWndEvents : public CWndContainer { public: //--- Event handlers of the chart void ChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- private: void ChartEventCustom(void); void ChartEventClick(void); void ChartEventMouseMove(void); void ChartEventObjectClick(void); void ChartEventEndEdit(void); void ChartEventChartChange(void); //--- Checking events in controls void CheckElementsEvents(void); };
Helper methods will be used inside the ChartEvent() method and only in the CWndEvents class. To avoid passing the same parameters to them, let us create similar variables in the form of class members and also a method for their initialization, which will be used in the very beginning of the ChartEvent() method. They will be located in the private section (private), as they are going to be used only in this class.
class CWndEvents : public CWndContainer { private: //--- Event parameters int m_id; long m_lparam; double m_dparam; string m_sparam; //--- Initialization of event parameters void InitChartEventsParams(const int id,const long lparam,const double dparam,const string sparam); }; //+------------------------------------------------------------------+ //| Initialization of event variables | //+------------------------------------------------------------------+ void CWndEvents::InitChartEventsParams(const int id,const long lparam,const double dparam,const string sparam) { m_id =id; m_lparam =lparam; m_dparam =dparam; m_sparam =sparam; }
Now, in the main program file, (1) include the file containing the CProgram class, (2) create its instance and (3) connect it with the main program functions:
//+------------------------------------------------------------------+ //| TestLibrary.mq5 | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2015, MetaQuotes Software Corp." #property link "http://www.mql5.com" //--- Including the trading panel class #include "Program.mqh" CProgram program; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(void) { program.OnInitEvent(); //--- Initialization successful return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { program.OnDeinitEvent(reason); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(void) { } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer(void) { program.OnTimerEvent(); } //+------------------------------------------------------------------+ //| Trade function | //+------------------------------------------------------------------+ void OnTrade(void) { } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { program.ChartEvent(id,lparam,dparam,sparam); } //+------------------------------------------------------------------+
If required, methods for other event handlers such as OnTick(), OnTrade(), etc. can be created in the CProgram class.
Test of Event Handlers of the Library and Program Classes
It was mentioned earlier that the virtual method OnEvent() of the CProgram class can be called from the base class CWndEvents in the method ChartEvent(). We want to make sure that this works and now we can test this mechanism. For that, in the CWndEvents::ChartEvent() method call CProgram::OnEvent() method the way it is shown below:
//+------------------------------------------------------------------+ //| Handling program events | //+------------------------------------------------------------------+ void CWndEvents::ChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { OnEvent(id,lparam,dparam,sparam); }
Then, in the CProgram::OnEvent() method, write the code below:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { if(id==CHARTEVENT_CLICK) { ::Comment("x: ",lparam,"; y: ",(int)dparam); } }
Compile files and load the EA on to the chart. If you left click on the chart, the cursor coordinates at the moment of releasing the mouse key will be displayed in the top left corner. After the tests were done, the code highlighted in the last two lists can be deleted from the CWndEvents::ChartEvent() and CProgram::OnEvent() methods.
Conclusion
To summarize where we are so far, let us show everything we have spoken about as a diagram:
Fig. 5. Inclusion into the project of the classes for storing pointers and event handling
Currently the scheme consists of two not interconnected parts. To connect them, first of all, it is required to create the main element of user interface. The main element is the form or window to which all other controls will be connected. Thus, we are going to write such a class and name it CWindow. Connect the file with the Element.mqh class to the file Window.mqh as the CElement class will be base for the CWindow class.
You can find material from Part I and download it to test how it works. If you have questions on using material from those files, you can refer to the detailed description of the library development in one of the articles from the list below or ask your question in the comments to this article.
List of articles (chapters) of the first part:
- Graphical Interfaces I: Preparation of the Library Structure (Chapter 1)
- Graphical Interfaces I: Form for Controls (Chapter 2)
- Graphical Interfaces I: Animating the Graphical Interface (Chapter 3)
- Graphical Interfaces I: Functions for the Form Buttons and Deleting Interface Elements (Chapter 4)
- Graphical Interfaces I: Testing Library in Programs of Different Types and in the MetaTrader 4 Terminal (Chapter 5)
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/2125





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
Hi. I have an error with: 'm_chart_id' - undeclared identifier Element.mqh 178 53 (x3)
Can you tell me how to solve it? Thank you