Improving Panels: Adding transparency, changing background color and inheriting from CAppDialog/CWndClient

27 July 2018, 16:50
Vladimir Karputov
0
2 154

Table of contents

Introduction

CAppDialog class based panels lack methods for direct access to the properties of controls which the panel is made of, such as the Background color and the Frame color. Therefore, all created panels are gray. 

It is not possible to implement design ideas without the ability to change the color of controls. This problem could be solved by inheriting and adding our own methods. But this solution would require a lot of corrections in the created code. Is there a simpler and faster way to access the "Background Color" and "Frame Color" properties for the panel controls?


Transparency of a panel when dragged

I will show first what can be done for the panel based on the CAppDialog class (this is a code example "Live panel.mq5").

Live panel

This gif image shows that if you drag the panel, only its outer border remains. When moving the panel, the color of the outer frame is additionally changed in an arbitrary order. When dragging is completed, the form becomes normal, with the filling of working areas.

Implementation

All work is connected with the CDialog class. It is located in [data folder]\MQL5\Include\Controls\Dialog.mqh

The below code shows the objects the panel consists of (the objects are declared in the CDialog class in the private section) and how they are visually implemented as graphic elements:

//+------------------------------------------------------------------+
//| Class CDialog                                                    |
//| Usage: base class to create dialog boxes                         |
//|             and indicator panels                                 |
//+------------------------------------------------------------------+
class CDialog : public CWndContainer
  {
private:
   //--- dependent controls
   CPanel            m_white_border;        // the "white border" object
   CPanel            m_background;          // the background object
   CEdit             m_caption;             // the window title object
   CBmpButton        m_button_close;        // the "Close" button object
   CWndClient        m_client_area;         // the client area object

protected:

CDialog objects

To make the panel transparent when dragging, we need to take into account four points.

1. We are interested in the Border and Back graphic elements (created by the m_white_border and m_background objects of the CPanel class) and the Client element (created by the m_client_area object of the CWndClient class). Functions CDialog::CreateWhiteBorder, CDialog::CreateBackground and CDialog::CreateClientArea show how the elements are created and what color is set for them.

2. During creation, the panel receives a unique name, i.e. a digital prefix which is added before the names of all graphic objects (the 03082 prefix is shown in the below figure):

Objects

3. Three dragging handlers are available in CDialog:

 Handling the dragging operation
 OnDialogDragStart  The virtual handler of the DialogDragStart event
 OnDialogDragProcess  The virtual handler of the DialogDragProcess event
 OnDialogDragEnd  The virtual handler of the DialogDragEnd event

4. Solution from the article "How to create a graphical panel of any complexity level" — iteration through panel objects (here ExtDialog is the panel class object):

   int total=ExtDialog.ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=ExtDialog.Control(i);
...

Why is the pointer to the obj object is declared with the CWnd type during iteration?

Because CWnd is the base class from which all other child classes originate:

Proceed with implementation

We will need two macros for working with colors. We will also need to redefine three functions from the CDialog class for intercepting the dragging of the panel:

//+------------------------------------------------------------------+
//|                                                   Live panel.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.000"
#property description "The panel transparency changes when dragging the panel"
#include <Controls\Dialog.mqh>
#define XRGB(r,g,b)    (0xFF000000|(uchar(r)<<16)|(uchar(g)<<8)|uchar(b))
#define GETRGB(clr)    ((clr)&0xFFFFFF)
//+------------------------------------------------------------------+
//| Class CLivePanel                                                 |
//| Usage: main dialog of the Controls application                   |
//+------------------------------------------------------------------+
class CLivePanel : public CAppDialog
  {
public:
                     CLivePanel(void);
                    ~CLivePanel(void);
   //--- create
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   //--- handlers of drag
   virtual bool      OnDialogDragStart(void);
   virtual bool      OnDialogDragProcess(void);
   virtual bool      OnDialogDragEnd(void);

  };

I will skip the standard part of panel operations (creation, deletion and passing of events). Let us dwell on the dragging event handlers in more detail.

OnDialogDragStart handler: panel dragging start

We obtain the prefix, and then loop through all objects of the panel and search for "Border", "Back" or "Client" with the prefix:

//+------------------------------------------------------------------+
//| Start dragging the dialog box                                    |
//+------------------------------------------------------------------+
bool CLivePanel::OnDialogDragStart(void)
  {
   string prefix=Name();
   int total=ExtDialog.ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=ExtDialog.Control(i);
      string name=obj.Name();
      //---
      if(name==prefix+"Border")
        {
         CPanel *panel=(CPanel*) obj;
         panel.ColorBackground(clrNONE);
         ChartRedraw();
        }
      if(name==prefix+"Back")
        {
         CPanel *panel=(CPanel*) obj;
         panel.ColorBackground(clrNONE);
         ChartRedraw();
        }
      if(name==prefix+"Client")
        {
         CWndClient *wndclient=(CWndClient*) obj;
         wndclient.ColorBackground(clrNONE);
         wndclient.ColorBorder(clrNONE);
         ChartRedraw();
        }
     }
   return(CDialog::OnDialogDragStart());
  }

As soon as the objects are found, we delete the background color (the ColorBackground method) and frame color (the ColorBorder method), by applying the clrNONE color. The transparency of the form is implemented in this way.

OnDialogDragProcess handler: panel dragging continuation

We search for only one object, i.e. "Back", and dynamically change its color (using two macros: XRGB and GETRGB):

//+------------------------------------------------------------------+
//| Continue dragging the dialog box                                 |
//+------------------------------------------------------------------+
bool CLivePanel::OnDialogDragProcess(void)
  {
   string prefix=Name();
   int total=ExtDialog.ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=ExtDialog.Control(i);
      string name=obj.Name();
      if(name==prefix+"Back")
        {
         CPanel *panel=(CPanel*) obj;
         color clr=(color)GETRGB(XRGB(rand()%255,rand()%255,rand()%255));
         panel.ColorBorder(clr);
         ChartRedraw();
        }
     }
   return(CDialog::OnDialogDragProcess());
  }

OnDialogDragEnd handler: panel dragging end

Now, we restore the color of the background and borders for the "Border", "Back" or "Client" objects:

//+------------------------------------------------------------------+
//| End dragging the dialog box                                      |
//+------------------------------------------------------------------+
bool CLivePanel::OnDialogDragEnd(void)
  {
   string prefix=Name();
   int total=ExtDialog.ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=ExtDialog.Control(i);
      string name=obj.Name();
      //---
      if(name==prefix+"Border")
        {
         CPanel *panel=(CPanel*) obj;
         panel.ColorBackground(CONTROLS_DIALOG_COLOR_BG);
         panel.ColorBorder(CONTROLS_DIALOG_COLOR_BORDER_LIGHT);
         ChartRedraw();
        }
      if(name==prefix+"Back")
        {
         CPanel *panel=(CPanel*) obj;
         panel.ColorBackground(CONTROLS_DIALOG_COLOR_BG);
         color border=(m_panel_flag) ? CONTROLS_DIALOG_COLOR_BG : CONTROLS_DIALOG_COLOR_BORDER_DARK;
         panel.ColorBorder(border);
         ChartRedraw();
        }
      if(name==prefix+"Client")
        {
         CWndClient *wndclient=(CWndClient*) obj;
         wndclient.ColorBackground(CONTROLS_DIALOG_COLOR_CLIENT_BG);
         wndclient.ColorBorder(CONTROLS_DIALOG_COLOR_CLIENT_BORDER);
         ChartRedraw();
        }
     }
   return(CDialog::OnDialogDragEnd());
  }

Adding a button to the panel, which becomes transparent during dragging

This example is available in the "Live panel and Button.mq5" code.

To be able to work with the button, we first need to include the button class to our Expert Advisor and add macros responsible for the button position and size:

#property description "The panel transparency changes when dragging the panel,"
#property description " but the color of the added button does not change"
#include <Controls\Dialog.mqh>
#include <Controls\Button.mqh>
#define XRGB(r,g,b)    (0xFF000000|(uchar(r)<<16)|(uchar(g)<<8)|uchar(b))
#define GETRGB(clr)    ((clr)&0xFFFFFF)
//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
//--- indents and gaps
#define INDENT_LEFT                         (11)      // indent from left (with allowance for border width)
#define INDENT_TOP                          (11)      // indent from top (with allowance for border width)
#define CONTROLS_GAP_X                      (5)       // gap by X coordinate
//--- for buttons
#define BUTTON_WIDTH                        (100)     // size by X coordinate
#define BUTTON_HEIGHT                       (20)      // size by Y coordinate
//+------------------------------------------------------------------+
//| Class CLivePanelAndButton                                        |
//| Usage: main dialog of the Controls application                   |
//+------------------------------------------------------------------+
class CLivePanelAndButton : public CAppDialog

Also, in order to be able to work with the button, we need to declare the CButton class object:

//+------------------------------------------------------------------+
//| Class CLivePanelAndButton                                        |
//| Usage: main dialog of the Controls application                   |
//+------------------------------------------------------------------+
class CLivePanelAndButton : public CAppDialog
  {
private:
   CButton           m_button1;                       // the button object

public:
                     CLivePanelAndButton(void);

and the button creation procedure:

   virtual bool      OnDialogDragEnd(void);

protected:
   //--- create dependent controls
   bool              CreateButton1(void);

  };

CreateButton1 code — after creating the button, do not forget to add the button to the panel:

//+------------------------------------------------------------------+
//| Create the "Button1" button                                      |
//+------------------------------------------------------------------+
bool CLivePanelAndButton::CreateButton1(void)
  {
//--- coordinates
   int x1=INDENT_LEFT;        // x1            = 11  pixels
   int y1=INDENT_TOP;         // y1            = 11  pixels
   int x2=x1+BUTTON_WIDTH;    // x2 = 11 + 100 = 111 pixels
   int y2=y1+BUTTON_HEIGHT;   // y2 = 11 + 20  = 32  pixels
//--- create
   if(!m_button1.Create(0,"Button1",0,x1,y1,x2,y2))
      return(false);
   if(!m_button1.Text("Button1"))
      return(false);
   if(!Add(m_button1))
      return(false);
//--- succeed
   return(true);
  }

Here is the result of adding a button to the above panel:

Live panel and Button

As you can see, the panel being dragged becomes transparent, while the added 'button' control remains opaque. Two options are available: making only the panel background transparent during dragging or making both the panel and the button transparent. Let us consider the second option: making the button look transparent when the panel is being dragged.

Making the button transparent when moving the panel

Let us do it using the code "Live panel and transparent Button.mq5".

When a simple or combined control is added to a panel, the object creating the control is added to the m_client_area object (remember, the m_client_area object is declared in the CDialog class). Therefore, when dragging of the panel is detected, we need to loop through all objects added to m_client_area. This can be conveniently done in the first handler OnDialogDragStart (panel dragging beginning):

//+------------------------------------------------------------------+
//| Start dragging the dialog box                                    |
//+------------------------------------------------------------------+
bool CLivePaneTransparentButton::OnDialogDragStart(void)
  {
   string prefix=Name();
   int total=ExtDialog.ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=ExtDialog.Control(i);
      string name=obj.Name();
      //---
      if(name==prefix+"Border")
        {
         CPanel *panel=(CPanel*) obj;
         panel.ColorBackground(clrNONE);
         ChartRedraw();
        }
      if(name==prefix+"Back")
        {
         CPanel *panel=(CPanel*) obj;
         panel.ColorBackground(clrNONE);
         ChartRedraw();
        }
      if(name==prefix+"Client")
        {
         CWndClient *wndclient=(CWndClient*) obj;
         wndclient.ColorBackground(clrNONE);
         wndclient.ColorBorder(clrNONE);
         //---
         int client_total=wndclient.ControlsTotal();
         for(int j=0;j<client_total;j++)
           {
            CWnd*client_obj=wndclient.Control(j);
            string client_name=client_obj.Name();
            if(client_name=="Button1")
              {
               CButton *button=(CButton*) client_obj;
               button.ColorBackground(clrNONE);
               ChartRedraw();
              }
           }
         ChartRedraw();
        }
     }
   return(CDialog::OnDialogDragStart());
  }

When dragging is detected for the first time, both the panel and the button will become transparent.

No need to change the second handler OnDialogDragProcess (continuation of panel dragging). The third handler OnDialogDragEnd (end of panel dragging) needs to be changed, because we need to set the button color back:

//+------------------------------------------------------------------+
//| End dragging the dialog box                                      |
//+------------------------------------------------------------------+
bool CLivePaneTransparentButton::OnDialogDragEnd(void)
  {
   string prefix=Name();
   int total=ExtDialog.ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=ExtDialog.Control(i);
      string name=obj.Name();
      //---
      if(name==prefix+"Border")
        {
         CPanel *panel=(CPanel*) obj;
         panel.ColorBackground(CONTROLS_DIALOG_COLOR_BG);
         panel.ColorBorder(CONTROLS_DIALOG_COLOR_BORDER_LIGHT);
         ChartRedraw();
        }
      if(name==prefix+"Back")
        {
         CPanel *panel=(CPanel*) obj;
         panel.ColorBackground(CONTROLS_DIALOG_COLOR_BG);
         color border=(m_panel_flag) ? CONTROLS_DIALOG_COLOR_BG : CONTROLS_DIALOG_COLOR_BORDER_DARK;
         panel.ColorBorder(border);
         ChartRedraw();
        }
      if(name==prefix+"Client")
        {
         CWndClient *wndclient=(CWndClient*) obj;
         wndclient.ColorBackground(CONTROLS_DIALOG_COLOR_CLIENT_BG);
         wndclient.ColorBorder(CONTROLS_DIALOG_COLOR_CLIENT_BORDER);
         //---
         int client_total=wndclient.ControlsTotal();
         for(int j=0;j<client_total;j++)
           {
            CWnd*client_obj=wndclient.Control(j);
            string client_name=client_obj.Name();
            if(client_name=="Button1")
              {
               CButton *button=(CButton*) client_obj;
               button.ColorBackground(CONTROLS_BUTTON_COLOR_BG);
               ChartRedraw();
              }
           }
         ChartRedraw();
        }
     }
   return(CDialog::OnDialogDragEnd());
  }

Now, changing of the panel dialog box and button color during panel dragging is fully implemented in "Live panel and transparent Button.mq5" code:

Live panel and transparent Button


Adding two buttons to the panel: setting panel background color and header caption color

This example is available in the code of "Live panel and button Clicks.mq5", and is created based on the previous code "Live panel and transparent Button.mq5". However, instead of panel dragging event, we need to "catch" button click events now. The panel will have two buttons: one button responsible for panel background change, the second one - changing of the caption color.

In order to catch events associated with controls on the panel, we need to declare an event handler and write the handler:

//+------------------------------------------------------------------+
//| Class CLivePaneButtonClicks                                      |
//| Usage: main dialog of the Controls application                   |
//+------------------------------------------------------------------+
class CLivePaneButtonClicks : public CAppDialog
  {
private:
   CButton           m_button1;                       // the button object
   CButton           m_button2;                       // the button object

public:
                     CLivePaneButtonClicks(void);
                    ~CLivePaneButtonClicks(void);
   //--- create
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   //--- chart event handler
   virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);

protected:
   //--- create dependent controls
   bool              CreateButton1(void);
   bool              CreateButton2(void);
   //--- handlers of the dependent controls events
   void              OnClickButton1(void);
   void              OnClickButton2(void);

  };
//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CLivePaneButtonClicks)
ON_EVENT(ON_CLICK,m_button1,OnClickButton1)
ON_EVENT(ON_CLICK,m_button2,OnClickButton2)
EVENT_MAP_END(CAppDialog)
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+

The handler is the OnEvent method, written using macros from the Events block and the "Macro of event handling map" block of the Defines.mqh file (for details see the article How to create a graphical panel of any complexity level).

The handler is interpreted as follows:

  • the OnEvent event for the CLivePaneButtonClicks class:
    • if there is a click on the m_button1 control, call the OnClickButton1 handler
    • if there is a click on the m_button2 control, call the OnClickButton2 handler
  • return of the OnEvent event for the parent CAppDialog class

OnClickButton1 and OnClickButton2 handlers

Both handlers contain loop through all objects which the panels consists of (they are listed in the Implementation paragraph) — in our case, loop through all objects of the ExtDialog panel object. As a result, the CWndContainer::ControlsTotal() method is called

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CLivePaneButtonClicks::OnClickButton1(void)
  {
   string prefix=Name();
   int total=ExtDialog.ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=ExtDialog.Control(i);
      string name=obj.Name();
      //---
      if(name==prefix+"Client")
        {
         CWndClient *wndclient=(CWndClient*) obj;
         color clr=(color)GETRGB(XRGB(rand()%255,rand()%255,rand()%255));
         wndclient.ColorBackground(clr);
         ChartRedraw();
         return;
        }
     }
  }
//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CLivePaneButtonClicks::OnClickButton2(void)
  {
   string prefix=Name();
   int total=ExtDialog.ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=ExtDialog.Control(i);
      string name=obj.Name();
      //---
      if(name==prefix+"Caption")
        {
         CEdit *edit=(CEdit*) obj;
         color clr=(color)GETRGB(XRGB(rand()%255,rand()%255,rand()%255));
         edit.ColorBackground(clr);
         ChartRedraw();
         return;
        }
     }
  }

In the OnClickButton1 handler, we search for the client area object with the name prefix+"Client" (this will be the CWndClient class object). In OnClickButton2 search for the header caption object named prefix+"Caption" (the CEdit class object). In both cases the background color for the found objects is selected randomly. Here is the result:

Live panel and button Clicks


Inheriting from CAppDialog

The implementation scheme differs from the one used in the standard library examples (\MQL5\Experts\Examples\Controls\ and \MQL5\Indicators\Examples\Panels\SimplePanel\). The difference is as follows: the CMyAppDialog class derived from CAppDialog is created in the MyAppDialog.mqh file. Only three methods for managing the form color and the caption color are implemented in the class. It does not have methods for creating added controls, OnEvent handler and button click handlers. 

CButton class objects (added controls - two buttons) are created in the main MyAppWindow.mq5 file. Also, button click events are monitored in the OnChartEvent handler of the MyAppWindow.mq5 file. Color changing methods are also called there.

MyAppDialog.mqh

Let us add three methods to our class: 

  • CMyAppDialog::ColorBackground — setting the background color,
  • void CMyAppDialog::ColorCaption — setting the caption color,
  • color CMyAppDialog::ColorCaption — getting the caption color.

The algorithm for accessing object properties is similar to the one used in previous codes: we loop through all objects which the panel is made of and compare object names. We need one more method for getting the background color. However, this can not be implemented using simple solutions.

//+------------------------------------------------------------------+
//|                                                  MyAppDialog.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property description "The CMyAppDialog class derived from CAppDialog"
#property description "Added methods for setting the background and caption colors"
#include <Controls\Dialog.mqh>
//+------------------------------------------------------------------+
//| Class CLivePanelTwoButtons                                       |
//| Usage: main dialog of the Controls application                   |
//+------------------------------------------------------------------+
class CMyAppDialog : public CAppDialog
  {
public:
   void              ColorBackground(const color clr);
   color             ColorCaption(void);
   void              ColorCaption(const color clr);
//--- Constructor and destructor   
public:
                     CMyAppDialog(void){};
                    ~CMyAppDialog(void){};
  };
//+------------------------------------------------------------------+
//| Sets background color                                            |
//+------------------------------------------------------------------+
void CMyAppDialog::ColorBackground(const color clr)
  {
   string prefix=Name();
   int total=ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=Control(i);
      string name=obj.Name();
      //---
      if(name==prefix+"Client")
        {
         CWndClient *wndclient=(CWndClient*) obj;
         wndclient.ColorBackground(clr);
         ChartRedraw();
         return;
        }
     }
//---     
  } 
//+------------------------------------------------------------------+
//| Sets caption color                                               |
//+------------------------------------------------------------------+
void CMyAppDialog::ColorCaption(const color clr)
  {
   string prefix=Name();
   int total=ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=Control(i);
      string name=obj.Name();
      //---
      if(name==prefix+"Caption")
        {
         CEdit *edit=(CEdit*) obj;
         edit.ColorBackground(clr);
         ChartRedraw();
         return;
        }
     }
//---     
  }  
//+------------------------------------------------------------------+
//| Gets the caption color                                           |
//+------------------------------------------------------------------+
color CMyAppDialog::ColorCaption(void)
  {
   string prefix=Name();
   int total=ControlsTotal();
   color clr=clrNONE;
   for(int i=0;i<total;i++)
     {
      CWnd*obj=Control(i);
      string name=obj.Name();
      //---
      if(name==prefix+"Caption")
        {
         CEdit *edit=(CEdit*) obj;
         clr=edit.ColorBackground(clr);
         return clr;
        }
     }
//--- Return the color
   return clr;     
  }  
//+------------------------------------------------------------------+


MyAppWindow.mq5

 "MyAppWindow.mq5" is the main file. XRGB and GETRGB macros for color generation are declared in this file. The panel is created, buttons are added and the panel is launched in OnInit.

//+------------------------------------------------------------------+
//|                                                  MyAppWindow.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property description "The MyAppWindow application based on the CMyAppDialog class"
#property description "Added buttons for setting the background and header colors"
#include "MyAppDialog.mqh"
#include <Controls\Button.mqh>
//--- Macros for working with the color
#define XRGB(r,g,b)    (0xFF000000|(uchar(r)<<16)|(uchar(g)<<8)|uchar(b))
#define GETRGB(clr)    ((clr)&0xFFFFFF)
//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
//--- indents and gaps
#define INDENT_LEFT                         (11)      // indent from left (with allowance for border width)
#define INDENT_TOP                          (11)      // indent from top (with allowance for border width)
#define CONTROLS_GAP_X                      (5)       // gap by X coordinate
//--- for buttons
#define BUTTON_WIDTH                        (100)     // size by X coordinate
#define BUTTON_HEIGHT                       (20)      // size by Y coordinate
//---
CMyAppDialog         AppWindow;
CButton              m_button1;                       // the button object
CButton              m_button2;                       // the button object
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create application dialog
   if(!AppWindow.Create(0,"CMyAppDialog: change Back and Caption colors",0,40,40,380,344))
      return(INIT_FAILED);
//--- create dependent controls
   if(!CreateBackButton())
      return(false);
   if(!CreateCaptionButton())
      return(false);
//--- run application
   AppWindow.Run();
//--- succeed
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   Comment("");
//--- destroy dialog
   AppWindow.Destroy(reason);
  }
//+------------------------------------------------------------------+
//| Expert chart event function                                      |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // event ID  
                  const long& lparam,   // event parameter of the long type
                  const double& dparam, // event parameter of the double type
                  const string& sparam) // event parameter of the string type
  {
//--- We handle button events first 
   if((StringFind(sparam,"Back")!=-1) && id==(CHARTEVENT_OBJECT_CLICK))
     {
      Print(__FUNCSIG__," sparam=",sparam);
      AppWindow.ColorBackground(GetRandomColor());
     }
   if((StringFind(sparam,"Caption")!=-1) && id==(CHARTEVENT_OBJECT_CLICK))
     {
      Print(__FUNCSIG__," sparam=",sparam);
      AppWindow.ColorCaption(GetRandomColor());
     }
//--- Then, all other events are processed by the CMyAppDialog class method   
   AppWindow.ChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+
//| Create the "Button1" button                                      |
//+------------------------------------------------------------------+
bool CreateBackButton(void)
  {
//--- coordinates
   int x1=INDENT_LEFT;        // x1            = 11  pixels
   int y1=INDENT_TOP;         // y1            = 11  pixels
   int x2=x1+BUTTON_WIDTH;    // x2 = 11 + 100 = 111 pixels
   int y2=y1+BUTTON_HEIGHT;   // y2 = 11 + 20  = 32  pixels
//--- create
   if(!m_button1.Create(0,"Back",0,x1,y1,x2,y2))
      return(false);
   if(!m_button1.Text("Back"))
      return(false);
   if(!AppWindow.Add(m_button1))
      return(false);
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Create the "Button2"                                             |
//+------------------------------------------------------------------+
bool CreateCaptionButton(void)
  {
//--- coordinates
   int x1=INDENT_LEFT+2*(BUTTON_WIDTH+CONTROLS_GAP_X);   // x1 = 11  + 2 * (100 + 5) = 221 pixels
   int y1=INDENT_TOP;                                    // y1                       = 11  pixels
   int x2=x1+BUTTON_WIDTH;                               // x2 = 221 + 100           = 321 pixels
   int y2=y1+BUTTON_HEIGHT;                              // y2 = 11  + 20            = 31  pixels
//--- create
   if(!m_button2.Create(0,"Caption",0,x1,y1,x2,y2))
      return(false);
   if(!m_button2.Text("Caption"))
      return(false);
   if(!AppWindow.Add(m_button2))
      return(false);
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Gets the color randomly                                          |
//+------------------------------------------------------------------+
color GetRandomColor()
  {
   color clr=(color)GETRGB(XRGB(rand()%255,rand()%255,rand()%255));
   return clr;
  }
//+------------------------------------------------------------------+

The handler of OnChartEvent events is also located in the main file. It unconditionally sends all events to the panel, but when a button click event is detected (CHARTEVENT_OBJECT_CLICK), it calls methods of the panel class (AppWindow.ColorBackground or AppWindow.ColorCaption).

This is how a bunch of two files works: the main mq5 file and the mqh include file, in which the panel class is located.


Inheriting from CWndClient

Let us consider inheritance from CWndClient in this class: we will create an object of the CWndClient class. The object will contain the following functionality:

  • creation of the CMyWndClient object, i.e. the client area of the panel;
  • creation of the object for adding two buttons in the client area;
  • handlers of clicks on the added buttons (changing the color of the client area background and the panel header color);
  • additionally, a horizontal scroll will be enabled for the client area (remember that the CWndClient class is a combined "Client area" control and is the base class for creating areas with scrolls);
  • accordingly, there will be handlers for clicks on the horizontal scroll (moving of added buttons within the client area).

Let us have a detailed look at files MyWndClient.mq5 and MyWndClient.mqh.

MyWndClient.mq5

The difference from the standard library examples (\MQL5\Experts\Examples\Controls\ and \MQL5\Indicators\Examples\Panels\SimplePanel\) is that the include file has a class derived from CWndClient 10 — the client area. The full panel dialog box creation cycle looks as follows.

  1. Creation of a panel (the AppWindow object of the CAppDialog class calls the Create method).
  2. Creation of our client area (the ClientArea object of the CMyWndClient class from the MyWndClient.mqh include file calls the Create method).
  3. Adding the created client area to the panel (actually applied over the client area of the panel).
CAppDialog           AppWindow;
CMyWndClient         ClientArea;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create application dialog
   bool result_create=false;
   if(!InpTwoButtonsVisible)
     {
      //--- after creation of the panel, one button will be visible
      result_create=AppWindow.Create(0,"CAppDialog with CMyWndClient",0,40,40,360,344);
     }
   else
     {
      //--- after creation of the panel, will two buttons are visible
      result_create=AppWindow.Create(0,"CAppDialog with CMyWndClient",0,40,40,420,344);
     }
   if(!result_create)
      return(INIT_FAILED);
//--- create the panel
   PrintFormat("Application Rect: Height=%d  Width=%d",AppWindow.Rect().Height(),AppWindow.Rect().Width());
   CRect inner_rect=ClientArea.GetClientRect(GetPointer(AppWindow));
   PrintFormat("Client Area: Height=%d  Width=%d",inner_rect.Height(),inner_rect.Width());
   ClientArea.Create(0,"MyWndClient",0,0,0,inner_rect.Width(),inner_rect.Height());
   AppWindow.Add(ClientArea);
//--- set the owner
   ClientArea.SetOwner(GetPointer(AppWindow));
//--- hide the invisible
   ClientArea.HideInvisble(HideInvisble);
//--- run application
   AppWindow.Run();
//--- succeed
   return(INIT_SUCCEEDED);
  }

The executable file has two input parameters:

  • panel width — creating a panel of a normal width or a wide panel;
  • hide the invisible — showing or hiding hidden controls.

Only the panel width parameter is taken into account when creating a panel. This is how a panel with the normal width is created:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create application dialog
   bool result_create=false;
   if(!InpTwoButtonsVisible)
     {
      //--- after creation of the panel, one button will be visible
      result_create=AppWindow.Create(0,"CAppDialog with CMyWndClient",0,40,40,360,344);
     }
   else
     {
      //--- after creation of the panel, will two buttons are visible
      result_create=AppWindow.Create(0,"CAppDialog with CMyWndClient",0,40,40,420,344);
     }
   if(!result_create)
      return(INIT_FAILED);
//--- create the panel

MyWndClient normal width panel

How to create a wide panel:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create application dialog
   bool result_create=false;
   if(!InpTwoButtonsVisible)
     {
      //--- after creation of the panel, one button will be visible
      result_create=AppWindow.Create(0,"CAppDialog with CMyWndClient",0,40,40,360,344);
     }
   else
     {
      //--- after creation of the panel, will two buttons are visible
      result_create=AppWindow.Create(0,"CAppDialog with CMyWndClient",0,40,40,420,344);
     }
   if(!result_create)
      return(INIT_FAILED);
//--- create the panel

MyWndClient wide panel

The panel creation code differs only in width (360 and 420). The width is not taken into account when creating two buttons. Compare the last two images. Now apply them one over another:

CMyWndClient imposition

As you can see, the "Caption" button does not within the borders of the panel with the normal width, i.e. does not fit into the client area. When the added control is beyond the boundaries, it is forcibly hidden from the user (but not deleted or destroyed). The procedure for determining whether a control should be hidden, starts when the control is being added to the client area, i.e. during the call of CWndContainer::Add. In our example, the 'Add' method is called in AddButton2:

//+------------------------------------------------------------------+
//| Create the "Button2"                                             |
//+------------------------------------------------------------------+
bool CMyWndClient::AddButton2(void)
  {
...
   if(!Add(m_button2))
     {
      Print("Add(m_button2) --> false");
      return(false);
     }
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Add control to the group (by reference)                          |
//+------------------------------------------------------------------+
bool CWndContainer::Add(CWnd &control)
  {
//--- add by pointer
   return(Add((CWnd*)GetPointer(control)));
  }

Then the most important parts are called sequentially - that is where the need to hide a control is determined

//+------------------------------------------------------------------+
//| Add control to the group (by pointer)                            |
//+------------------------------------------------------------------+
bool CWndContainer::Add(CWnd *control)
  {
//--- check of pointer
   if(control==NULL)
      return(false);
//--- correct the coordinates of added control
   control.Shift(Left(),Top());
 //--- "projecting" the group flag "visibility" to the added element
   if(IS_VISIBLE && control.IsVisible())
     {
      //--- element will be "visible" only if the group is "visible" and the element is completely "within" this group
      control.Visible(Contains(control));
     }
   else
      control.Hide();
//--- "projecting" the group flag "enabled" to the added element
   if(IS_ENABLED)
      control.Enable();
   else
      control.Disable();
//--- adding
   return(m_controls.Add(control));
  }

The object visibility is only set at the time the control is being added to the client area. This is the disadvantage which can be revealed after minimizing and maximizing the panel.

Example: set the "false" value for both input parameters, then minimize the panel and maximize it. As a result, the Caption button is created after panel creation, but it is visually hidden (the button does not fit into the client area, so it is hidden from it). But after minimizing and maximizing the panel, the visibility of added controls is no longer checked, therefore the Caption button will be visible:

CMyWndClient button caption

The file contains the class derived from the CWndClient class of the client area. Required functionality is contained in this file:

  • creating our client area, 
  • creating and adding controls, 
  • handling the panel maximizing event
  • handling the event of a click on the horizontal scroll bar
  • handling button click events — changing the color of the client area background and the panel caption color.

Horizontal scroll and hidden controls

Since the panel class is derived from CWndClient, and CWndClient is the "Client area" combined control and is a base class for creating areas with scroll bars, we need to enable the horizontal scroll bar in our client area:

//+------------------------------------------------------------------+
//| Creating a panel                                                 |
//+------------------------------------------------------------------+
bool CMyWndClient::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2)
  {
//---
   if(!CWndClient::Create(chart,name,subwin,x1,y1,x2,y2))
      return(false);
//--- enable horizontal scrollbar
   if(!HScrolled(true))
      return(false);
   m_scroll_h.MaxPos(5);
   m_scroll_h.CurrPos(5);
   Print("CurrPos: ",m_scroll_h.CurrPos());
   if(!AddButton1())

Let's set gradations for the scroll to make it movable: maximum position value, and position the scroll in the rightmost position

The horizontal scroll is also used for detecting the panel maximizing event. For this purpose we need to "catch" the ON_SHOW event for the m_scroll_h object and call the OnShowScrollH handler:

//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CMyWndClient)
ON_EVENT(ON_CLICK,m_button1,OnClickButton1)
ON_EVENT(ON_CLICK,m_button2,OnClickButton2)
ON_EVENT(ON_SHOW,m_scroll_h,OnShowScrollH)
EVENT_MAP_END(CWndClient)

EVENT_MAP_BEGIN is actually the OnEvent method, written using macros from the Events block and the Macro of event handling map block of the Defines.mqh file (for details see the article How to create a graphical panel of any complexity level).

In the OnShowScrollH handler, we need to check the value of the internal m_hide_invisble flag (the flag value is equal to the input variable "hide the invisible" of the  MyWndClient.mq5 file, via the CMyWndClient::HideInvisble method). If the controls do not need to be hidden, exit the procedure:

//+------------------------------------------------------------------+
//| A scroll appeared, show/hide buttons                             |
//+------------------------------------------------------------------+
void CMyWndClient::OnShowScrollH(void)
  {
   if(!m_hide_invisble)
      return;
   int total=CWndClient::ControlsTotal();
   for(int i=0;i<total;i++)
     {
      CWnd*obj=Control(i);
      string name=obj.Name();
      //---
      if(StringFind(name,"Button")!=-1)
        {
         CButton *button=(CButton*)obj;
         button.Visible(Contains(GetPointer(button)));
         ChartRedraw();
        }
     }
  }

If invisible elements need to be hidden, then loop through all objects of the client area to find objects with names containing "Button" and forcibly check/set visibility.

Clicks on the horizontal scroll

Let's set the possibility to move two buttons along the client area for the horizontal scroll clicks. For this purpose we override OnScrollLineRight and OnScrollLineLeft, which are handlers of horizontal scroll button clicks. If the right horizontal scroll button is clicked, move buttons (using the ShiftButton method) using the m_scroll_size step. If the left button is clicked, move buttons using a step of "-m_scroll_size", i.e. apply a negative shift:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
bool CMyWndClient::OnScrollLineRight(void)
  {
   Print(__FUNCTION__);
   ShiftButton(GetPointer(m_button1),m_scroll_size);
   ShiftButton(GetPointer(m_button2),m_scroll_size);
   return(true);
  }
//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
bool CMyWndClient::OnScrollLineLeft(void)
  {
   Print(__FUNCTION__);
   ShiftButton(GetPointer(m_button1),-m_scroll_size);
   ShiftButton(GetPointer(m_button2),-m_scroll_size);
   return(true);
  }
In the CMyWndClient::ShiftButton method, we receive the object with the button coordinates, set the shift for coordinates, shift the button and forcibly check/set the button visibility:

//+------------------------------------------------------------------+
//| Button shift to the left or right (depending on shift)           |
//+------------------------------------------------------------------+
bool CMyWndClient::ShiftButton(CButton *button,const int shift)
  {
   Print(__FUNCTION__);
//--- shift the button
   CRect rect=button.Rect();
   rect.Move(rect.left+shift,rect.top);
   button.Move(rect.left,rect.top);
   button.Visible(Contains(GetPointer(button)));
   return(true);
  }

Here is how it looks like (do not forget to set the "hide the invisible" parameter to true):

CMyWndClient move buttons



New Projects. How can they help in studying panels?

You always have to learn code in order to write something. When it comes to creating a panel, studying classes may require much time and effort. This is mainly due to the fact that there is no visual representation of the structure of classes. It may be difficult to understand, what standard library classes are used for creating panels. 

Fortunately, New projects were presented in the MetaEditor not so long ago.

A project is a separate file with the MQPROJ extension, which stores program settings, compilation parameters and information about all files used in the project. A separate tab in the Navigator is provided for a convenient work with the project. All files, such as include, resource, header and other files are arranged into categories on this tab.

Pay attention to the description of the separately created tab: "All files, such as include, resource, header and other files are arranged into categories on this tab."! This is exactly what we need!

Let's try to create a project using the last file "Live panel and button Clicks.mq5". Right click on the "Live panel and button Clicks.mq5" panel and select "New Project from Source":


As a result, a new project will be created, and the Project tab will open in the Navigator. The tab features all the files used:


as well as Wnd.mqh (containing the CWnd class), Dialog.mqh (containing the CDialog and CAppDialog classes) and Button.mhq (containing CButton). From this tab, you can conveniently switch to the required class. This is much more convenient than navigating through MetaEditor tabs. For example, I have a selection of a variety of files. It is difficult to go to a desired file, say Dialog.mqh, using search in tabs:


Conclusion

The article shows a rather unusual way to access the "Background Color", "Frame Color" and "Header Color" properties of the panel controls. I have never seen such a way before. The point is in understanding that all panel elements are derived from the parent CWnd class, so the object, which is the class of the created panel, is the container for all controls. So, you can loop through all controls and get/set desired properties.

File name Comment
 Live panel.mq5  A panel without added controls. Becomes transparent when dragging
 Live panel and Button.mq5  A panel with an added button. During dragging, the panel becomes transparent and the button preserves the color
 Live panel and transparent Button.mq5  A panel with an added button. During dragging, both the panel and the button become transparent
 Live panel and button Clicks.mq5  A panel with two buttons. Handling button clicks: generation of the background color for the panel and the header
 MyAppWindow.mq5 and MyAppDialog.mqh  Example of panel creation though inheritance from CAppDialog
 MyWndClient.mq5 and MyWndClient.mqh  Example of panel creation though inheritance from the client area class CWndClient

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

Attached files |
MQL5.zip (18.28 KB)
Social Trading. Can a profitable signal be made even better? Social Trading. Can a profitable signal be made even better?

Most subscribers choose a trade signal by the beauty of the balance curve and by the number of subscribers. This is why many today's providers care of beautiful statistics rather than of real signal quality, often playing with lot sizes and artificially reducing the balance curve to an ideal appearance. This paper deals with the reliability criteria and the methods a provider may use to enhance its signal quality. An exemplary analysis of a specific signal history is presented, as well as methods that would help a provider to make it more profitable and less risky.

Visual strategy builder. Creating trading robots without programming Visual strategy builder. Creating trading robots without programming

This article presents a visual strategy builder. It is shown how any user can create trading robots and utilities without programming. Created Expert Advisors are fully functional and can be tested in the strategy tester, optimized in the cloud or executed live on real time charts.

How to analyze the trades of the Signal selected in the chart How to analyze the trades of the Signal selected in the chart

The trade Signals service develops in leaps and bounds. Trusting our funds to a signal provider, we would like to minimize the risk of losing our deposit. So how to puzzle out in this forest of trade signals? How to find the one that would produce profits? This paper proposes to create a tool for visually analyzing the history of trades on trade signals in a symbol chart.

Expert Advisor featuring GUI: Creating the panel (part I) Expert Advisor featuring GUI: Creating the panel (part I)

Despite the fact that many traders still prefer manual trading, it is hardly possible to completely avoid the automation of routine operations. The article shows an example of developing a multi-symbol signal Expert Advisor for manual trading.