Graphical Interfaces IV: the Multi-Window Mode and System of Priorities (Chapter 2)

Anatoli Kazharski | 19 April, 2016

Contents


Introduction

The first article Graphical Interfaces I: Preparation of the Library Structure (Chapter 1) explains in detail what this library is for. You will find a list of articles with links 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 chapter, we discussed the status bar and tooltip informational elements of the graphical interface. In this chapter, we will extend the library implementation to the possibility of creating multi-window interfaces for the MQL applications. In addition to that, we will develop a system of priorities for the left mouse button click on graphical objects as without that it may occur that controls do not respond to the user's actions.


The Multi-Window Mode

Let us consider the multi-window mode of the graphical interface of the library under development. Up to now, the ENUM_WINDOW_TYPE enumeration provided two identifiers for the main (W_MAIN) and dialog (W_DIALOG) windows. The single-window mode was the only mode in use. After we introduce some additions, enabling the multi-window mode will simply involve the creation and addition of the required number of control forms to the base.

In the main class for event handling CWndEvents create a field for storing the index of the currently active window.

class CWndEvents : public CWndContainer
  {
protected:
   //--- Index of the active window
   int               m_active_window_index;
  };

Let us see how the index of the active window is going to be identified. For instance, the user assigns the opening of a dialog window (W_DIALOG) to some button. When the button is pressed, the ON_CLICK_BUTTON custom event is generated. This event can be tracked in the CProgram::OnEvent() event handler of the custom class. We will also use the CWindow::Show() method of the form which is to be shown. It is not sufficient in the current implementation of the library and we will introduce necessary additions.

A custom event will have to be sent from the CWindow::Show() method that will indicate that a window has been opened so parameter values of the graphical interface system must be updated. Such an event requires a separate identifier. Let us call it ON_OPEN_DIALOG_BOX and place it to the Defines.mqh file where other library identifiers are located. 

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#define ON_OPEN_DIALOG_BOX        (11) // The opening of a dialog window event

Add a line at the very end of the CWindow::Show() method as shown in the code below. This is a shortened version of the method. For the unambiguous identification of the event initiator, the element identifier and program name must be sent in addition to the event identifier.

//+------------------------------------------------------------------+
//| Shows the window                                                 |
//+------------------------------------------------------------------+
void CWindow::Show(void)
  {
//--- Make all objects visible
//--- State of visibility
//--- Zeroing the focus
//--- Send a message about it
   ::EventChartCustom(m_chart_id,ON_OPEN_DIALOG_BOX,(long)CElement::Id(),0,m_program_name);
  }

This event will be handled in the CWndEvents class. Before the implementation of the method for handling, we need to create three more methods in the CWindow class. These are two methods for storing and getting the index of the form from which a dialog window will be opened as well as a method for managing the state of the form. 

The index of the previously active window must be stored as several windows may be opened simultaneously. This is why when closing a dialog window, it is important to know which of them will have to be returned to the active state. 

class CWindow : public CElement
  {
private:
   //--- Index of the previously active window
   int               m_prev_active_window_index;
   //---
public:
   //--- (1) Storing and (2) getting the index of the previously active window
   void              PrevActiveWindowIndex(const int index)                  { m_prev_active_window_index=index;   }
   int               PrevActiveWindowIndex(void)                       const { return(m_prev_active_window_index); }
  };

As for managing the state of the form, deactivated forms will have a different header color, which can be changed by the user. The color of elements will not change when the mouse cursor is hovering over them as the form will be blocked. Adding to that, at the moment of deactivation of the form, a custom event will be generated. This will communicate that the form is blocked and the focuses and colors of its elements must be zeroed. When the form is blocked, the focus on the elements is not tracked. At the moment of opening a dialog window, the color of the element that brought up the window will be as if the mouse cursor is still hovering over it. 

The ON_RESET_WINDOW_COLORS identifier is be created in the Defines.mqh file for such an event:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#define ON_RESET_WINDOW_COLORS    (13) // Zeroing colors of all elements on the form

Method for managing the state of the form is presented in the code below.

class CWindow : public CElement
  {
public:
   //--- Setting the state of the window
   void              State(const bool flag);
  };
//+------------------------------------------------------------------+
//| Setting the state of the window                                  |
//+------------------------------------------------------------------+
void CWindow::State(const bool flag)
  {
//--- If the window is to be blocked
   if(!flag)
     {
      //--- Set the status
      m_is_locked=true;
      //--- Set the color of the header
      m_caption_bg.BackColor(m_caption_bg_color_off);
      //--- Signal to zero the color. The reset will be performed for other elements too.
      ::EventChartCustom(m_chart_id,ON_RESET_WINDOW_COLORS,(long)CElement::Id(),0,"");
     }
//--- If the window is to be unblocked
   else
     {
      //--- Set the status
      m_is_locked=false;
      //--- Set the color of the header
      m_caption_bg.BackColor(m_caption_bg_color);
      //--- Zero the focus
      CElement::MouseFocus(false);
     }
  }

Let us return to the handling of the ON_OPEN_DIALOG_BOX event. In the main class for handling the events of the graphical interface (CWndEvents) create the CWndEvents::OnOpenDialogBox() method, which will be called in the CWndEvents::ChartEventCustom() common method for handling all custom events.

The CWndEvents::OnOpenDialogBox() method begins with two checks: one for the event identifier and one for the program name. If they have been passed, then iterate over all windows to find out what window generated the event. The element identifier that is contained in this message (lparam) will facilitate this. The forms that do not have matching identifiers will be blocked together with all the elements attached to them. Priorities of all objects will be zeroed with the help of the ResetZorders() method and will not react to the left mouse click. Having made it to the form that has matching identifiers, store the index of the currently active window as the index of the previously active window. Activate this form and restore the priority of the left mouse click to all its objects. Store the index of this window as currently active. Then, make all elements of this form visible and restore their priorities of the left mouse click, omitting the form element as it is already visible and drop-down elements. 

It a dialog window is open when a tooltip is visible, then the tooltip must be hidden. It will not disappear by itself as the form to which it is attached is already blocked. The private array for tooltips was created earlier to cater for such cases. Access to methods of any elements in a base can be received in the CWndEvents main class for event handling. 

class CWndEvents : public CWndContainer
  {
private:
   //--- Opening a dialog window
   bool              OnOpenDialogBox(void);
  };
//+------------------------------------------------------------------+
//| CHARTEVENT_CUSTOM event                                          |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- If the signal is to minimize the form
//--- If the signal is to maximize the form
//--- If the signal is to hide the context menus below the initiating menu item
//--- If the signal is to hide all context menus

//--- If the signal is to open a dialog window
   if(OnOpenDialogBox())
      return;
  }
//+------------------------------------------------------------------+
//| ON_OPEN_DIALOG_BOX event                                         |
//+------------------------------------------------------------------+
bool CWndEvents::OnOpenDialogBox(void)
  {
//--- If the signal is to open a dialog window
   if(m_id!=CHARTEVENT_CUSTOM+ON_OPEN_DIALOG_BOX)
      return(false);
//--- Leave, if the message is from another program
   if(m_sparam!=m_program_name)
      return(true);
//--- Iterate over the window array
   int window_total=CWndContainer::WindowsTotal();
   for(int w=0; w<window_total; w++)
     {
      //--- If identifiers match
      if(m_windows[w].Id()==m_lparam)
        {
         //--- Store the index of the window in the form from which the form was brought up
         m_windows[w].PrevActiveWindowIndex(m_active_window_index);
         //--- Activate the form
         m_windows[w].State(true);
         //--- Restore priorities of the left mouse click to the form objects
         m_windows[w].SetZorders();
         //--- Store the index of the activated window
         m_active_window_index=w;
         //--- Make all elements of the activated window visible
         int elements_total=CWndContainer::ElementsTotal(w);
         for(int e=0; e<elements_total; e++)
           {
            //--- Skip the forms and drop-down elements
            if(m_wnd[w].m_elements[e].ClassName()=="CWindow" || 
               m_wnd[w].m_elements[e].IsDropdown())
               continue;
            //--- Make the element visible
            m_wnd[w].m_elements[e].Show();
            //--- Restore the priority of the left mouse click to the element
            m_wnd[w].m_elements[e].SetZorders();
           }
         //--- Hiding tooltips
         int tooltips_total=CWndContainer::TooltipsTotal(m_windows[w].PrevActiveWindowIndex());
         for(int t=0; t<tooltips_total; t++)
            m_wnd[m_windows[w].PrevActiveWindowIndex()].m_tooltips[t].FadeOutTooltip();
        }
      //--- Other forms will be blocked until the activated window is closed
      else
        {
         //--- Block the form
         m_windows[w].State(false);
         //--- Zero priorities of the left mouse click for the form elements
         int elements_total=CWndContainer::ElementsTotal(w);
         for(int e=0; e<elements_total; e++)
            m_wnd[w].m_elements[e].ResetZorders();
        }
     }
//---
   return(true);
  }

Now, we are going to address the ON_RESET_WINDOW_COLORS identifier which was created earlier in this article. Before writing a method for handling this event, one more standard virtual method must be added to the CElement base class of all elements which will be designated for zeroing the color. Let us name it CElement::ResetColors():

class CElement
  {
public:
   //--- Zeroing the element color
   virtual void      ResetColors(void) {}
  };

The ResetColors() methods with characteristics specific to each element must be created in all derived classes. The code below shows an example for the icon button element (CIconButton). The ResetColors() method for all other elements can be found in the files attached to this article.

class CIconButton : public CElement
  {
public:
   //--- Zeroing the element color
   void              ResetColors(void);
  };
//+------------------------------------------------------------------+
//| Zeros the color                                                  |
//+------------------------------------------------------------------+
void CIconButton::ResetColors(void)
  {
//--- Leave, if this is the two-state mode and the button is pressed
   if(m_two_state && m_button_state)
      return;
//--- Zero the color
   m_button.BackColor(m_back_color);
//--- Zero the focus
   m_button.MouseFocus(false);
   CElement::MouseFocus(false);
  }

Hence, a virtual method in the base class of elements and its own versions in derived classes provide a possibility to zero the colors of all elements in one loop from the event handler of the main class of the library (CWndEvents).

Write the CWndEvents::OnResetWindowColors() method for handling the ON_RESET_WINDOW_COLORS event. It is rather simple. Look for the form that has just been deactivated by the element identifier that has just been received in a message. If there is one, store its index. If the index was stored, zero colors of all elements in this form. Details of this method can be found in the code below. 

class CWndEvents : public CWndContainer
  {
private:
   //--- Zeroing the color of the form and its elements
   bool              OnResetWindowColors(void);
  };
//+------------------------------------------------------------------+
//| CHARTEVENT_CUSTOM event                                          |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- If the signal is to minimize the form
//--- If the signal is to maximize the form
//--- If the signal is to hide the context menus below the initiating menu item
//--- If the signal is to hide all context menus
//--- If the signal is to open a dialog window
//--- If the signal is to zero the colors of all elements on the specified form
   if(OnResetWindowColors())
      return;
  }
//+------------------------------------------------------------------+
//| ON_RESET_WINDOW_COLORS event                                     |
//+------------------------------------------------------------------+
bool CWndEvents::OnResetWindowColors(void)
  {
//--- If the signal is to zero the window color
   if(m_id!=CHARTEVENT_CUSTOM+ON_RESET_WINDOW_COLORS)
      return(false);
//--- To identify the index of the form from which the message was received
   int index=WRONG_VALUE;
//--- Iterate over the window array
   int window_total=CWndContainer::WindowsTotal();
   for(int w=0; w<window_total; w++)
     {
      //--- If identifiers match
      if(m_windows[w].Id()==m_lparam)
        {
         //--- Store the index
         index=w;
         //--- Zero the color of the form
         m_windows[w].ResetColors();
         break;
        }
     }
//--- Leave, if the index was not identified
   if(index==WRONG_VALUE)
      return(true);
//--- Zero colors of all form elements
   int elements_total=CWndContainer::ElementsTotal(index);
   for(int e=0; e<elements_total; e++)
      m_wnd[index].m_elements[e].ResetColors();
//--- Redrawing of the chart
   m_chart.Redraw();
   return(true);
  }

We have clarified the opening of windows. Now, we have to implement the methods for closing and restoring the previously active window. To handle this event, we have to create the ON_CLOSE_DIALOG_BOX identifier in the Defines.mqh file

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#define ON_CLOSE_DIALOG_BOX       (12) // Closing of a dialog window event

In the CWindow class we are using the CWindow::CloseWindow() method for closing the form and the program together with it. In this method the section that closes the dialog windows (W_DIALOG) is not implemented yet. Let us write an additional method that will generate an event for closing dialog windows. In addition to (1) the event identifier, the message will also contain (2) the element identifier, (3) the index of the previously active window and (4) the text of the header. Let us call this method CWindow::CloseDialogBox(). Later, we will also use this in complex controls where the closing of a window will be carried out by elements other than the closing button.

class CWindow : public CElement
  {
public:
   //--- Closing a dialog window
   void              CloseDialogBox(void);
  };
//+------------------------------------------------------------------+
//| Closing a dialog window                                          |
//+------------------------------------------------------------------+
void CWindow::CloseDialogBox(void)
  {
//--- State of visibility
   CElement::IsVisible(false);
//--- Send a message about it
   ::EventChartCustom(m_chart_id,ON_CLOSE_DIALOG_BOX,CElement::Id(),m_prev_active_window_index,m_caption_text);
  }

In the CWindow class, the CWindow::CloseDialogBox() method is to be called in the CWindow::CloseWindow() method as shown in the shortened version of the code below. A complete version can be found in the files attached to this article.

//+------------------------------------------------------------------+
//| Closing a dialog window or the program                           |
//+------------------------------------------------------------------+
bool CWindow::CloseWindow(const string pressed_object)
  {
//--- If the press was not on the close window button
   if(pressed_object!=m_button_close.Name())
      return(false);
//--- If this is the main window
   if(m_window_type==W_MAIN)
     {
      //--- ...
     }
//--- If this is a dialog window
   else if(m_window_type==W_DIALOG)
     {
      //--- Close it
      CloseDialogBox();
     }
//---
   return(false);
  }

After the message with the ON_CLOSE_DIALOG_BOX identifier has been sent, it has to be tracked and handled in the handler of the CWndEvents class. For that, let us write the CWndEvents::OnCloseDialogBox() method. Iterate over all windows in the base and look for the one with the identifier matching the identifier in the message. If such a window is found, it is to be deactivated. Then, hide it together with all the elements attached to it and activate the form by the index passed in the message. After that, store the index of the currently active window and restore the priorities of the left mouse click for the elements.

class CWndEvents : public CWndContainer
  {
private:
   //--- Closing a dialog window
   bool              OnCloseDialogBox(void);
  };
//+------------------------------------------------------------------+
//| CHARTEVENT_CUSTOM event                                          |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- If the signal is to minimize the form
//--- If the signal is to maximize the form
//--- If the signal is to hide the context menus below the initiating menu item
//--- If the signal is to hide all context menus
//--- If the signal is to open a dialog window
//--- If the signal is to close a dialog window
   if(OnCloseDialogBox())
      return;
//--- If the signal is to zero the colors of all elements on the specified form
  }
//+------------------------------------------------------------------+
//| ON_CLOSE_DIALOG_BOX event                                        |
//+------------------------------------------------------------------+
bool CWndEvents::OnCloseDialogBox(void)
  {
//--- If the signal is to close a dialog window
   if(m_id!=CHARTEVENT_CUSTOM+ON_CLOSE_DIALOG_BOX)
      return(false);
//--- Iterate over the window array
   int window_total=CWndContainer::WindowsTotal();
   for(int w=0; w<window_total; w++)
     {
      //--- If identifiers match
      if(m_windows[w].Id()==m_lparam)
        {
         //--- Block the form
         m_windows[w].State(false);
         //--- Hide the form
         int elements_total=CWndContainer::ElementsTotal(w);
         for(int e=0; e<elements_total; e++)
            m_wnd[w].m_elements[e].Hide();
         //--- Activate the previous form
         m_windows[int(m_dparam)].State(true);
         //--- Redrawing of the chart
         m_chart.Redraw();
         break;
        }
     }
//--- Setting the index of the previous window
   m_active_window_index=int(m_dparam);
//--- Restoring priorities of the left mouse click to the activated window
   int elements_total=CWndContainer::ElementsTotal(m_active_window_index);
   for(int e=0; e<elements_total; e++)
      m_wnd[m_active_window_index].m_elements[e].SetZorders();
//---
   return(true);
  }

Now, everything is ready for testing of the multi-window mode.  

 


Test of the Multi-Window Mode

Create two instances of the CWindow class in the EA that we used for testing of the informational interface elements. The result will be three forms in the graphical interface of the EA. The first form will be the main one (W_MAIN) and two others will have a role of dialog windows (W_DIALOG). Attach the first dialog window to one of the buttons on the main form. Create three buttons in the first dialog window and attach the second dialog window to one of the newly created buttons. This way we will have three forms opened simultaneously and only one of them will be active (available).

The code below shows what needs to be added to the CProgram custom class of the application at the current stage of development. 

class CProgram : public CWndEvents
  {
private:
   //--- Form 2
   CWindow           m_window2;
   //--- Icon buttons
   CIconButton       m_icon_button6;
   CIconButton       m_icon_button7;
   CIconButton       m_icon_button8;

   //--- Form 3
   CWindow           m_window3;
   //---
private:
   //--- Form 2
   bool              CreateWindow2(const string text);
   //--- Icon buttons
#define ICONBUTTON6_GAP_X        (7)
#define ICONBUTTON6_GAP_Y        (25)
   bool              CreateIconButton6(const string text);
#define ICONBUTTON7_GAP_X        (7)
#define ICONBUTTON7_GAP_Y        (50)
   bool              CreateIconButton7(const string text);
#define ICONBUTTON8_GAP_X        (7)
#define ICONBUTTON8_GAP_Y        (75)
   bool              CreateIconButton8(const string text);

   //--- Form 3
   bool              CreateWindow3(const string text);
  };

Locate calling of these methods in the main method of creating the graphical interface of the application under development. Below is a shortened version of this method. 

//+------------------------------------------------------------------+
//| Creates the trading panel                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Creating form 1 for controls
//--- Creating controls:
//    Main menu
//--- Context menus
//--- Creating the status bar
//--- Icon buttons

//--- Creating form 2 for controls
   if(!CreateWindow2("Icon Button 1"))
      return(false);
//--- Icon buttons
   if(!CreateIconButton6("Icon Button 6..."))
      return(false);
   if(!CreateIconButton7("Icon Button 7"))
      return(false);
   if(!CreateIconButton8("Icon Button 8"))
      return(false);

//--- Creating form 3 for controls
   if(!CreateWindow3("Icon Button 6"))
      return(false);

//--- Tooltips
//--- Redrawing of the chart
   m_chart.Redraw();
   return(true);
  }

We will consider the method only for the first dialog window (second form). As you remember, you need to use the CWndContainer::AddWindow() method for adding a form to the base. Please note how form coordinates are defines in the code below. As the default coordinates are zero when the program is loaded on to the chart, the coordinates you consider suitable will be set. In this example the values are x=1, y=20. After that, the form can be moved and then the timeframe or the symbol of the chart can be switched. The code below shows that the form will stay where it was the last time. If you want the form to be located where it was at the first loading of the program to the chart, then remove these conditions. In this example, all three forms of the graphical interface of the program will have the same conditions.

Let us arrange that the dialog forms can be moved over the chart. The window type should be set as dialog (W_DIALOG), otherwise you will encounter incorrect work of the graphical interface. The window icon can be redefined using the CWindow::IconFile() method. In case of dialog windows, same icon can be used as the one of the element that brings up this window.

//+------------------------------------------------------------------+
//| Creates form 2 for controls                                      |
//+------------------------------------------------------------------+
bool CProgram::CreateWindow2(const string caption_text)
  {
//--- Add the window pointer to the window array
   CWndContainer::AddWindow(m_window2);
//--- Coordinates
   int x=(m_window2.X()>0) ? m_window2.X() : 1;
   int y=(m_window2.Y()>0) ? m_window2.Y() : 20;
//--- Properties
   m_window2.Movable(true);
   m_window2.WindowType(W_DIALOG);
   m_window2.XSize(160);
   m_window2.YSize(160);
   m_window2.IconFile("Images\\EasyAndFastGUI\\Icons\\bmp16\\coins.bmp");
   m_window2.CaptionBgColor(clrCornflowerBlue);
   m_window2.CaptionBgColorHover(C'150,190,240');
//--- Creating the form
   if(!m_window2.CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
//---
   return(true);
  }

Let me remind you about some details of how to attach controls to a certain dialog window. As an example, let us consider one of the button methods designated for this form. I would like to highlight only two things. 

You need to remember that:

//+------------------------------------------------------------------+
//| Creates icon button 6                                            |
//+------------------------------------------------------------------+
bool CProgram::CreateIconButton6(const string button_text)
  {
//--- Store the window pointer
   m_icon_button6.WindowPointer(m_window2);
//--- Coordinates
   int x=m_window2.X()+ICONBUTTON6_GAP_X;
   int y=m_window2.Y()+ICONBUTTON6_GAP_Y;
//--- Set properties before the creation
   m_icon_button6.TwoState(false);
   m_icon_button6.ButtonXSize(146);
   m_icon_button6.ButtonYSize(22);
   m_icon_button6.LabelColor(clrBlack);
   m_icon_button6.LabelColorPressed(clrBlack);
   m_icon_button6.BorderColorOff(clrWhite);
   m_icon_button6.BackColor(clrLightGray);
   m_icon_button6.BackColorHover(C'193,218,255');
   m_icon_button6.BackColorPressed(C'153,178,215');
   m_icon_button6.IconFileOn("Images\\EasyAndFastGUI\\Icons\\bmp16\\script.bmp");
   m_icon_button6.IconFileOff("Images\\EasyAndFastGUI\\Icons\\bmp16\\script_colorless.bmp");
//--- Create control
   if(!m_icon_button6.CreateIconButton(m_chart_id,m_subwin,button_text,x,y))
      return(false);
//--- Add the element pointer to the base
   CWndContainer::AddToElementsArray(1,m_icon_button6);
   return(true);
  }

It is up to the developer of the application to manage the display of windows. Track the pressing on any control in the event handler of the CProgram custom class and show the relevant window. Assign the call of the first dialog window to the button on the main window of the EA (second form), and the call of the second dialog window to the button on the first dialog window (third form).

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- The button press event
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- If the text matches
      if(sparam==m_icon_button1.Text())
        {
         //--- Show window 2
         m_window2.Show();
        }
      //--- If the text matches
      if(sparam==m_icon_button6.Text())
        {
         //--- Show window 3
         m_window3.Show();
        }
     }
  }

Desired result is shown in the screenshot below. Please note the ellipsis in the button names «Icon Button 1...» and «Icon Button 6...». This is a usual way of letting the user know that pressing of this element will open a dialog window.

Fig. 1. Testing of the multi-window mode.

Fig. 1. Testing of the multi-window mode.

 

If you switch the symbol or the timeframe of the chart when several forms are open, you will encounter a problem. Dialog windows will disappear as they are supposed to but the management will not be passed over to the main window. The form will not respond to the user's actions. The solution to this is simple. As you remember, the CWndEvents::Destroy() method is called in the CProgram::OnDeinitEvent() method for uninitialization of the custom class. The graphical interface of the application is deleted in this method. The management has to be given to the main window at the moment of deleting the graphical interface. Therefore, some additions must be introduced to the CWndEvents::Destroy() method:

Below is the code of the current version of the CWndEvents::Destroy() method. 

//+------------------------------------------------------------------+
//| Deleting all objects                                             |
//+------------------------------------------------------------------+
void CWndEvents::Destroy(void)
  {
//--- Set the index of the main window
   m_active_window_index=0;
//--- Get the number of windows
   int window_total=CWndContainer::WindowsTotal();
//--- Iterate over the window array
   for(int w=0; w<window_total; w++)
     {
      //--- Activate the main window
      if(m_windows[w].WindowType()==W_MAIN)
         m_windows[w].State(true);
      //--- Block dialog windows
      else
         m_windows[w].State(false);
     }
//--- Empty element arrays
   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 following
         if(::CheckPointer(m_wnd[w].m_elements[e])==POINTER_INVALID)
            continue;
         //--- Delete element objects
         m_wnd[w].m_elements[e].Delete();
        }
      //--- Empty element arrays
      ::ArrayFree(m_wnd[w].m_objects);
      ::ArrayFree(m_wnd[w].m_elements);
      ::ArrayFree(m_wnd[w].m_context_menus);
     }
//--- Empty form arrays
   ::ArrayFree(m_wnd);
   ::ArrayFree(m_windows);
  }

The first version of the multi-window mode is implemented. Everything turned out to be less complicated than it could have seemed initially.  

 


Enhancement of the System of Priorities of the Left Mouse Button

Up until now, the management of priorities of the left mouse click on the interface elements has been carried out by the events with the ON_OPEN_DIALOG_BOX and ON_CLOSE_DIALOG_BOX identifiers. The reason for that was that when the next drop-down element was developed, it was up to the user to assign the priority value for each object of this element. Priorities of other elements that could happen to be beneath it were taken into account. However, when it came to creating complex compound controls, this system was awkward and easy to confuse. To make things easier, let us create two more identifiers for such events:

Add them to the Defines.mqh file.

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#define ON_ZERO_PRIORITIES        (14) // Resetting priorities of the left mouse button
#define ON_SET_PRIORITIES         (15) // Restoring priorities of the left mouse click

Generation of events with these identifiers must be located in the classes of the elements that are or can be drop-down ones. At the current stage of development, in the present interface set, context menu is such an element. Therefore, add the code to the Show() and Hide() methods of the CContextMenu class as shown below in the shortened versions of the code.

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

//--- Send a signal for zeroing priorities of the left mouse click
   ::EventChartCustom(m_chart_id,ON_ZERO_PRIORITIES,CElement::Id(),0.0,"");
  }
//+------------------------------------------------------------------+
//| Hides the context menu                                           |
//+------------------------------------------------------------------+
void CContextMenu::Hide(void)
  {
//--- Leave, if the element is hidden
//--- Hide the objects of the context menu
//--- Hide the menu items
//--- Zero the focus
//--- State of the context menu
//--- Register the state in the previous node

//--- Send a signal to restore the priorities of the left mouse click
   ::EventChartCustom(m_chart_id,ON_SET_PRIORITIES,0,0.0,"");
  }

We will receive these messages in the main class for handling all messages (CWndEvents). For that, we will write a separate handling method for each identifier. These methods will be called in the main method for handling custom events CWndEvents::ChartEventCustom(). 

class CWndEvents : public CWndContainer
  {
private:
   //--- Resetting priorities of the left mouse button click
   bool              OnZeroPriorities(void);
   //--- Restoring priorities of the left mouse click
   bool              OnSetPriorities(void);
  };
//+------------------------------------------------------------------+
//| CHARTEVENT_CUSTOM event                                          |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- If the signal is to minimize the form
//--- If the signal is to maximize the form
//--- If the signal is to hide the context menus below the initiating menu item
//--- If the signal is to hide all context menus
//--- If the signal is to open a dialog window
//--- If the signal is to close a dialog window
//--- If the signal is to zero the colors of all elements on the specified form

//--- If the signal is to reset the priorities of the left mouse button click
   if(OnZeroPriorities())
      return;
//--- If the signal is to restore the priorities of the left mouse button click
   if(OnSetPriorities())
      return;
  }

In the CWndEvents::OnZeroPriorities() method, iterate over all elements of the active window and zero priorities of all of them except the one with the element identifier contained in the message (lparam-parameter) as well as except menu items and context menus. The reason why we exclude menu items and context menus is that several context menus can be opened at the same time (one from another).

//+------------------------------------------------------------------+
//| ON_ZERO_PRIORITIES event                                         |
//+------------------------------------------------------------------+
bool CWndEvents::OnZeroPriorities(void)
  {
//--- If the signal is to zero priorities of the left mouse click
   if(m_id!=CHARTEVENT_CUSTOM+ON_ZERO_PRIORITIES)
      return(false);
//---
   int elements_total=CWndContainer::ElementsTotal(m_active_window_index);
   for(int e=0; e<elements_total; e++)
     {
      //--- Zero priorities of all elements except the one with the id passed in the event and ...
      if(m_lparam!=m_wnd[m_active_window_index].m_elements[e].Id())
        {
         //--- ... except context menus
         if(m_wnd[m_active_window_index].m_elements[e].ClassName()=="CMenuItem" ||
            m_wnd[m_active_window_index].m_elements[e].ClassName()=="CContextMenu")
            continue;
         //---
         m_wnd[m_active_window_index].m_elements[e].ResetZorders();
        }
     }
//---
   return(true);
  }

If the received message contains the ON_SET_PRIORITIES event identifier, then restore priorities of the left mouse button click for all the elements of the active window. 

//+------------------------------------------------------------------+
//| ON_SET_PRIORITIES event                                          |
//+------------------------------------------------------------------+
bool CWndEvents::OnSetPriorities(void)
  {
//--- If the signal is to restore the priorities of the left mouse button click
   if(m_id!=CHARTEVENT_CUSTOM+ON_SET_PRIORITIES)
      return(false);
//---
   int elements_total=CWndContainer::ElementsTotal(m_active_window_index);
   for(int e=0; e<elements_total; e++)
      m_wnd[m_active_window_index].m_elements[e].SetZorders();
//---
   return(true);
  }

 


Conclusion

The library for creating graphical interfaces at the current stage of development looks like in the schematic below.

Fig. 2. Library structure at the current stage of development.

Fig. 2. Library structure at the current stage of development.

 

This is the final article of the fourth part of the series about graphical interfaces. In the first chapter of this part, the status bar and tooltip informational interface elements were realized. In the second chapter, the multi-window mode and a priority system of the left mouse click were discussed.

You can find and download all the material of the first part or the series in the attached files so you can test how it works. 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 fourth part: