Download MetaTrader 5

Graphical Interfaces I: Functions for the Form Buttons and Deleting Interface Elements (Chapter 4)

18 February 2016, 11:30
Anatoli Kazharski
0
8 344

Content

 

Introduction

This article is the continuation of the first part of the series about graphical interfaces. The first article Graphical Interfaces I: Preparation of the Library Structure (Chapter 1) explains in detail what this library is for. A complete list of links to the articles of the first part is at the end of each chapter. There, you can also download a complete version of the library at the current stage of development. The files must be placed in the same directories as they are located in the archive.

In the previous article, the CWindow class was extended with additions that allowed moving the form over a chart. Now, the form controls react to mouse cursor movements. In this article, we will continue the development of the CWindow class and enrich it with methods that will permit us to manage the form by clicking on its controls. We will enable the program to be closed by a form button as well as implement a minimizing and maximizing feature for the form.

 

Functions for the Form Buttons

The form in the library under development contains two main buttons, which are compulsory for an EA and an indicator, and an additional one, which may not be shown at all.

  1. The first button on the right must contain the close window function. It should be specified here how exactly this will be implemented. If the button that is pressed is in a dialog window that has been called from the main window or another dialog window, then this dialog window will be closed. If the button that is pressed is in the main window, then this program will be deleted from the chart.

  2. The second button consists of two parts. To be precise, this is two buttons. Each of them is displayed depending on the current mode. If the window is maximized, then the button for minimizing the window will be shown. A minimized window will have a button for maximizing the window.

  3. The third button is designed for enabling the custom tooltip mode. Some clarification is required as to what "custom tooltips" means. The MQL language allows to reflect text tooltips for each graphical object in the focus of the mouse cursor.

    You must have noticed that in the code of the CWindow class the CChartObject::Tooltip() method was used for creating each object of the form when the properties of the object were specified. If the object is required to have a tooltip, then the text that will be shown when the cursor is hovered over the object must be passed to the Tooltip() method. If the tooltip is not required, then the "\n" text must be passed.

    This property has some limitations: (1) if the text contains more than 128 symbols, it cannot be shown completely and (2) properties of the text such as stroke weight, color, font cannot be managed. Thus, these are not custom tooltips. We will class them as MQL standard tooltips. Such tooltips are good for small comments. They do not suit, however, when the text is significantly larger and has to be shown in several lines with some words highlighted.

    I am sure that everyone is familiar with Excel. As an example, we can see what tooltips for all options look like in this program (see the screenshot below). A class that will allow showing tooltips of this kind will be created in the library under development. Those tooltips will be called custom as we will implement the functionality for their creation ourselves. We will come back to this matter only after the form class for controls is fully implemented and there is at least one control that can be attached to the form.

Fig. 1. Tooltips in Excel

Fig. 1. Tooltips in Excel

Let us start with the button for closing the window. Create the CloseWindow() method in the CWindow class. It is to be called inside the CWindow::OnEvent() chart event handler when handling an event with the CHARTEVENT_OBJECT_CLICK identifier caused by clicking on a graphical object. An event with this identifier is generated when clicking on any graphical object. At that time, the string parameter of the chart events (sparam) contains the name of the object that was clicked on. For that reason, a check for the object name has to be carried out to make sure that this is the object in question.

The CWindow::CloseWindow() method accepts one parameter — the name of the object that was clicked on. At the beginning of the code there is a check if the click was on the object, which by our design is a button for closing the window. Then the code of the method is divided into two branches — for the main and the dialog window. As the multi-window mode is not complete yet, the branch for the dialog window is going to be left empty. We will get back to it for implementing later. Everything is ready for work with the main window. That is why checks for the type of the program (whether this is an EA or an indicator) are required in the body of this condition. This is because the deletion of different types of programs requires different functions of the MQL language.

MQL has its own function for calling a dialog window for confirming various actions of the user. This is the MessageBox() function. We are going to use this temporarily for the confirmation of deleting an EA from the chart. This window cannot be used for deleting an indicator as this belongs to the modal type. The reason being is that indicators are executed in the interface stream and we should not suspend it. When the multi-window mode and the "Button" control are implemented, we will be able to use our dialog window, that is the window created with the means of our library.

Before the program is deleted from the chart, a message that the program was deleted due to the user's decision will be printed in the Experts journal of the terminal.

Let us introduce additions to the CWindow class as shown in the code below.

Declaration and implementation of the CWindow::CloseWindow() method for closing a window:

class CWindow: public CElement
  {
public:
   //--- Closing the window
   bool              CloseWindow(const string pressed_object);
  };
//+------------------------------------------------------------------+
//| Closing the dialog window or the program                         |
//+------------------------------------------------------------------+
bool CWindow::CloseWindow(const string pressed_object)
  {
//--- If the click was not on the button for closing the window
   if(pressed_object!=m_button_close.Name())
      return(false);
//--- If this is the main window
   if(m_window_type==W_MAIN)
     {
      //--- If the program is of the "Expert Advisor" type
      if(CElement::ProgramType()==PROGRAM_EXPERT)
        {
         string text="Do you want the program to be deleted from the chart?";
         //--- Open a dialog window
         int mb_res=::MessageBox(text,NULL,MB_YESNO|MB_ICONQUESTION);
         //--- If the button "Yes" is pressed, delete the program from the chart
         if(mb_res==IDYES)
           {
            ::Print(__FUNCTION__," > The program was deleted from the chart due to your decision!");
            //--- Deleting the Expert Advisor from the chart
            ::ExpertRemove();
            return(true);
           }
        }
      //--- If the program is of the "Indicator" type
      else if(CElement::ProgramType()==PROGRAM_INDICATOR)
        {
         //--- Deleting the indicator from the chart
         if(::ChartIndicatorDelete(m_chart_id,m_subwin,CElement::ProgramName()))
           {
            ::Print(__FUNCTION__," > The program was deleted from the chart due to your decision!");
            return(true);
           }
        }
     }
   //--- If this is a dialog window
   else if(m_window_type==W_DIALOG)
     {
     }
//---
   return(false);
  }

Calling the CWindow::CloseWindow() method in the chart event handler:

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handling event of clicking on an object
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Close the window
      CloseWindow(sparam);
      return;
     }
  }

Compile files of the library and the EA that you used before for tests. Load the EA on to the chart. Pressing the button for closing the window on the form now will bring up a window as shown in the screenshot:

Fig. 2. Test of closing the program by pressing a button on the form

Fig. 2. Test of closing the program by pressing a button on the form

Now, we are going to create methods for the second button, which will allow maximizing and minimizing the form:

  • The CWindow::RollUp() method for minimizing the form.
  • The CWindow::Unroll() method for maximizing the form.

These methods are very simple and similar in their content. At the beginning of each method, the icon is changed, then the size (height) of the background is set up and stored, then the focus is zeroed and a status corresponding to the user's choice is set. When the program is an indicator located in any window of the chart other than the main one, the fixed height mode of the indicator sub-window is set at creation of the form and the mode allowing to change the size of the indicator sub-window is set, then the CWindow::ChangeSubwindowHeight() method is called. This was created earlier and should be in the CWindow class.

The code of the CWindow::RollUp() and CWindow::Unroll() methods is presented in detail below:

//+------------------------------------------------------------------+
//| Minimizes the window                                             |
//+------------------------------------------------------------------+
void CWindow::RollUp(void)
  {
//--- Change the button
   m_button_rollup.Timeframes(OBJ_NO_PERIODS);
   m_button_unroll.Timeframes(OBJ_ALL_PERIODS);
//--- Set and store the size
   m_bg.Y_Size(m_caption_height);
   CElement::YSize(m_caption_height);
//--- Disable the button
   m_button_unroll.MouseFocus(false);
   m_button_unroll.State(false);
//--- State of the form "Minimized"
   m_is_minimized=true;
//--- If this is an indicator with a set height and with the sub-window minimization mode,
//    set the size of the indicator sub-window
   if(m_height_subwindow_mode)
      if(m_rollup_subwindow_mode)
         ChangeSubwindowHeight(m_caption_height+3);
  }
//+------------------------------------------------------------------+
//| Maximizes the window                                             |
//+------------------------------------------------------------------+
void CWindow::Unroll(void)
  {
//--- Change the button
   m_button_unroll.Timeframes(OBJ_NO_PERIODS);
   m_button_rollup.Timeframes(OBJ_ALL_PERIODS);
//--- Set and store the size
   m_bg.Y_Size(m_bg_full_height);
   CElement::YSize(m_bg_full_height);
//--- Disable the button
   m_button_rollup.MouseFocus(false);
   m_button_rollup.State(false);
//--- State of the form "Maximized"
   m_is_minimized=false;
//--- If this is an indicator with a set height and with the sub-window minimization mode,
//    set the size of the indicator sub-window
   if(m_height_subwindow_mode)
      if(m_rollup_subwindow_mode)
         ChangeSubwindowHeight(m_subwindow_height);
  }

Now, we have to create one more method accepting the string parameter of the chart events. The check for the name of the object clicked on will be carried out with the string parameter similar to the way it was implemented in the CWindow::CloseWindow() method. Depending on which button was pressed, the corresponding method considered in the code above will be called. Let us name this method CWindow::ChangeWindowState(). Add this declaration, implementation and call to the chart event handler in the CWindow class as shown in the code below:

class CWindow: public CElement
  {
public:
   //--- Changing the window state
   bool              ChangeWindowState(const string pressed_object);
  };
//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handling event of clicking on an object
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Minimize/Maximize the window
      ChangeWindowState(sparam);
      return;
     }
  }
//+------------------------------------------------------------------+
//| Check for the window minimization/maximization event             |
//+------------------------------------------------------------------+
bool CWindow::ChangeWindowState(const string pressed_object)
  {
//--- If the button "Minimize the window" was pressed
   if(pressed_object==m_button_rollup.Name())
     {
      RollUp();
      return(true);
     }
//--- If the button "Maximize the window" was pressed
   if(pressed_object==m_button_unroll.Name())
     {
      Unroll();
      return(true);
     }
//---
   return(false);
  }

Compile the library files where you introduced changes and the program file intended for tests. The expected result of our work is the possibility to minimize/maximize the form for controls. The screenshot below shows the form in the minimized mode:

Fig. 3. Test of the form functionality.

Fig. 3. Test of the form functionality

This works. The form for controls can be moved over the chart, every object reacts to the mouse cursor and the buttons work according to their designed functionality.

 

Deletion of Interface Elements

If you followed the sequence of actions suggested in the article and made it to this point, you could see that when the EA is deleted from the chart, all the object of the graphical interface are deleted. We have not discussed methods for deleting graphical objects from the chart yet. Why do the objects get deleted when the EA is deleted? This is provided in the standard library of classes, to be precise, in the destructor of the CChartObject class, the derived classes of which are used in our library. When the program is deleted from the chart, destructors of classes, including this one are called. If an object is attached to this chart, it gets deleted:

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CChartObject::~CChartObject(void)
  {
   if(m_chart_id!=-1)
      ::ObjectDelete(m_chart_id,m_name);
  }

If the chart symbol or its timeframe is changed when the EA is on the chart, then destructors are not called and graphical objects are not deleted. As the graphical interface is created in the OnInit() initialization function in the main program file, and uninitialization and then initialization are carried out at the change of the symbol or the EA timeframe, the graphical interface is created on top of the existing one. As a result, the first instance of such a change will give you two copies of the objects. If you continue changing the chart symbol or timeframe, you will have many copies of interface objects.

Fig. 4. Test of the form when switching the chart symbol and timeframe.

Fig. 4. Test of the form when switching the chart symbol and timeframe

We must factor this in in the library we are developing, that is we must ensure that when the program is uninitialized, all graphical objects are deleted. Also, all arrays containing pointers to these objects are to be emptied and accept the size of their dimensions as zero. After that has been done, at the moment of initialization, setting up objects will be correct, that is without creating clones. Now, we are going to implement this mechanism.

The Delete() virtual method is declared in the CElement class. Every class derived from the CElement class will have its own implementation of this method. The Delete() method was declared earlier in the CWindow class. Now, we only have to implement this method (see the code below). In this method some variables are zeroed, all control objects are deleted and also array of pointers to the objects in the base class are emptied.

//+------------------------------------------------------------------+
//| Deleting                                                         |
//+------------------------------------------------------------------+
void CWindow::Delete(void)
  {
//--- Zeroing variables
   m_right_limit=0;
//--- Deleting objects
   m_bg.Delete();
   m_caption_bg.Delete();
   m_icon.Delete();
   m_label.Delete();
   m_button_close.Delete();
   m_button_rollup.Delete();
   m_button_unroll.Delete();
   m_button_tooltip.Delete();
//--- Emptying the object array
   CElement::FreeObjectsArray();
//--- Zeroing the control focus
   CElement::MouseFocus(false);
  }

Access to the Delete() methods of all interface controls can be obtained from the CWndEvents class. Therefore, we will create the CWndEvents::Destroy() method for deleting the graphical interface. In this method iterate over the controls of all forms, calling the Delete() method of every control. Before calling the Delete() method, check the pointer for validity. After the objects are deleted, control arrays must be emptied. After leaving the form loop, their arrays must be emptied too.

The code below shows the declaration and implementation of the CWndEvents::Destroy() method:

class CWndEvents : public CWndContainer
  {
protected:
   //--- Deleting the interface
   void              Destroy(void);
  };
//+------------------------------------------------------------------+
//| Deleting all objects                                             |
//+------------------------------------------------------------------+
void CWndEvents::Destroy(void)
  {
   int window_total=CWndContainer::WindowsTotal();
   for(int w=0; w<window_total; w++)
     {
      int elements_total=CWndContainer::ElementsTotal(w);
      for(int e=0; e<elements_total; e++)
        {
         //--- If the pointer is invalid, move to the next
         if(::CheckPointer(m_wnd[w].m_elements[e])==POINTER_INVALID)
            continue;
         //--- Delete control objects
         m_wnd[w].m_elements[e].Delete();
        }
      //--- Empty control arrays
      ::ArrayFree(m_wnd[w].m_objects);
      ::ArrayFree(m_wnd[w].m_elements);
     }
//--- Empty form arrays
   ::ArrayFree(m_wnd);
   ::ArrayFree(m_windows);
  }

Now, call the Destroy() method inside the CProgram::OnDeinitEvent() method connected with the OnDeinit() function in the main program file. Add it there as shown below:

//+------------------------------------------------------------------+
//| Uninitialization                                                 |
//+------------------------------------------------------------------+
void CProgram::OnDeinitEvent(const int reason)
  {
   //--- Deleting the interface
   CWndEvents::Destroy();  
  }

Compile all library files where the last changes were made and the main program file. Load the EA on to the chart and change the chart symbol and timeframe a few times. Everything should work correctly now. Object clones do not appear any more. Problem solved.

 

Conclusion

In the next chapter we will perform tests to see how the form works in other program types such as indicators and scripts. We will also conduct tests in the MetaTrader 4 terminal as the initial goal was to make the library for creating graphical interfaces cross-platform.

You can find material from Part I below 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 of this article.

List of articles (chapters) of the first part:

Translated from Russian by MetaQuotes Software Corp.
Original article: https://www.mql5.com/ru/articles/2128

Attached files |
Step on New Rails: Custom Indicators in MQL5 Step on New Rails: Custom Indicators in MQL5

I will not list all of the new possibilities and features of the new terminal and language. They are numerous, and some novelties are worth the discussion in a separate article. Also there is no code here, written with object-oriented programming, it is a too serous topic to be simply mentioned in a context as additional advantages for developers. In this article we will consider the indicators, their structure, drawing, types and their programming details, as compared to MQL4. I hope that this article will be useful both for beginners and experienced developers, maybe some of them will find something new.

Here Comes the New MetaTrader 5 and MQL5 Here Comes the New MetaTrader 5 and MQL5

This is just a brief review of MetaTrader 5. I can't describe all the system's new features for such a short time period - the testing started on 2009.09.09. This is a symbolical date, and I am sure it will be a lucky number. A few days have passed since I got the beta version of the MetaTrader 5 terminal and MQL5. I haven't managed to try all its features, but I am already impressed.

Using text files for storing input parameters of Expert Advisors, indicators and scripts Using text files for storing input parameters of Expert Advisors, indicators and scripts

The article describes the application of text files for storing dynamic objects, arrays and other variables used as properties of Expert Advisors, indicators and scripts. The files serve as a convenient addition to the functionality of standard tools offered by MQL languages.

How to create an indicator of non-standard charts for MetaTrader Market How to create an indicator of non-standard charts for MetaTrader Market

Through offline charts, programming in MQL4, and reasonable willingness, you can get a variety of chart types: "Point & Figure", "Renko", "Kagi", "Range bars", equivolume charts, etc. In this article, we will show how this can be achieved without using DLL, and therefore such "two-for-one" indicators can be published and purchased from the Market.