Improving Panels: Adding transparency, changing background color and inheriting from CAppDialog/CWndClient
Table of contents
- Introduction
- Transparency of a panel when dragged
- Implementation
- Proceed with implementation
- OnDialogDragStart handler: panel dragging start
- OnDialogDragProcess handler: panel dragging continuation
- OnDialogDragEnd handler: panel dragging end
- Adding a button to the panel, which becomes transparent during dragging
- Adding two buttons to the panel: setting panel background color and header caption color
- Inheriting from CAppDialog
- Inheriting from CWndClient
- New Projects. How can they help in studying panels?
- Conclusion
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").
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.
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:
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):
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:
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:
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:
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:
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.
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" 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.
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.
- Creation of a panel (the AppWindow object of the CAppDialog class calls the Create method).
- Creation of our client area (the ClientArea object of the CMyWndClient class from the MyWndClient.mqh include file calls the Create method).
- 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
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
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:
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:
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):
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 Ltd.
Original article: https://www.mql5.com/ru/articles/4575
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use