Mejorando el trabajo con Paneles: cómo añadir transparencia, cambiar el color del fondo y heredar a partir de CAppDialog/CWndClient
Contenido
- Introducción
- Transparencia del panel durante el desplazamiento
- Cómo se hace
- Ahora podemos proceder a la implementación
- El controlador "OnDialogDragStart": comienzo del desplazamiento del panel
- El controlador "OnDialogDragProcess": continuamos desplazando el panel
- El controlador "OnDialogDragEnd": finalizamos el desplazamiento del panel
- Agregamos un botón al panel y hacemos que el botón sea transparente al mover el panel
- Añadimos dos botones al panel: controlamos el color de fondo del panel y el color del encabezado
- Heredamos de CAppDialog
- Heredamos de CWndClient
- MyWndClient.mq5
- Desplazamiento horizontal y elementos de control ocultos
- Clics de desplazamiento horizontal
- Nuevos proyectos. ¿Cómo pueden ayudarnos en el estudio de los paneles?
- Conclusión
Introducción
Los paneles basados en la clase CAppDialog no tienen métodos suficientes de acceso directo a las propiedades de los elementos de control de los que consta el panel, tales como el "Color de fondo" y el "Color del marco". Por lo tanto, todos los paneles se crean igualmente grises.
Al no tener forma de cambiar el color de los controles, no podemos implementar las ideas del diseño. Por supuesto que podemos resolver este problema mediante la herencia y la adición de sus métodos. Pero para ello será necesario hacer bastantes correcciones al código generado. ¿Existe alguna forma más simple y rápida de acceder a las propiedades "Color de fondo" y "Color del marco" para los elementos de control del panel?
Transparencia del panel durante el desplazamiento
Vamos a mostrar en primer lugar qué podemos hacer para el panel basado en la clase CAppDialog (se trata de un ejemplo de funcionamiento del código "Live panel.mq5").
Esta animación se muestra que, al desplazar el panel, solo queda el marco exterior del mismo. Durante el desplazamiento, el marco externo también cambiará su color en un orden arbitrario. Cuando se completa el desplazamiento, la forma vuelve a ser normal: aparecen las áreas de trabajo.
Todo el trabajo se hará alrededor de la clase CDialog. Esta se ubica en el archivo [data folder]\MQL5\Include\Controls\Dialog.mqh.
Recordemos de qué objetos consta el panel (estos objetos están declarados en la clase CDialog, en la sección privada) y cómo se incorporan visualmente estos a los elementos gráficos://+------------------------------------------------------------------+ //| 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:
Para dar transparencia al panel durante al arrastre, debemos tener en cuenta cuatro momentos clave.
1. A nosotros nos interesan los elementos "Border" y "Back" (creados por los objetos m_white_border y m_background de la clase CPanel) y el elemento "Client" (creado por el objeto m_client_area de la clase CWndClient). Podemos ver cómo se crean y qué color se les asigna en las funciones CDialog::CreateWhiteBorder, CDialog::CreateBackground y CDialog::CreateClientArea, respectivamente.
2. Durante su creación, el panel recibe un nombre único, es decir, un prefijo digital que se coloca antes de los nombres de todos los objetos gráficos (en la figura siguiente, el prefijo es 03082):
3. En la clase CDialog hay tres procesadores de arrastre:
Procesamiento del arrastre | |
---|---|
OnDialogDragStart | Procesador virtual del evento "DialogDragStart" del elemento de control |
OnDialogDragProcess | Procesador virtual del evento "DialogDragProcess" del elemento de control |
OnDialogDragEnd | Procesador virtual del evento "DialogDragEnd" del elemento de control |
4. El truco del artículo "Cómo crear un panel gráfico de cualquier nivel de complejidad" consiste en la iteración de los objetos del panel (aquí ExtDialog es un objeto de clase del panel):
int total=ExtDialog.ControlsTotal(); for(int i=0;i<total;i++) { CWnd*obj=ExtDialog.Control(i); ...
¿Por qué al iterar el ciclo, el puntero al objeto obj se declara con el tipo CWnd?
Porque CWnd es una clase básica, de la cual proceden todas las clases descendientes:
Ahora podemos proceder a la implementación
Para trabajar con el color, necesitamos dos macros, y para captar el desplazamiento del panel, tenemos que redefinir tres funciones de la clase CDialog:
//+------------------------------------------------------------------+ //| 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 "El panel cambia su transparencia al desplazarse" #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); };
Vamos a omitir la parte estándar del trabajo con el panel (creación, eliminación y transmisión de eventos), deteniéndonos en los controles de desplazamiento con más detalle.
El controlador "OnDialogDragStart": comienzo del desplazamiento del panel
Obtenemos el prefijo, después, en el ciclo iteramos todos los objetos del panel y buscamos el nombre "Border", "Back" o "Client" con el prefijo:
//+------------------------------------------------------------------+ //| 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()); }
Cuando los objetos han sido localizados, eliminamos el color del fondo (método "ColorBackground") y los marcos (método "ColorBorder"), adoptando el color clrNONE. De esta forma, se implementa la transparencia de la forma.
El controlador "OnDialogDragProcess": continuamos desplazando el panel
Solo buscamos un objeto — "Back", y cambiamos dinámicamente su color (con la ayuda de dos macros, XRGB y 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()); }
El controlador "OnDialogDragEnd": finalizamos el desplazamiento del panel
Restablecemos el color del fondo y los marcos con los objetos "Border", "Back" o "Client":
//+------------------------------------------------------------------+ //| 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()); }
Agregamos un botón al panel y hacemos que el botón sea transparente al mover el panel
Este ejemplo se contiene en el código "Live panel and Button.mq5"
Para trabajar con el botón, primero es necesario incluir en nuestro asesor la clase del botón y agregar las macros responsables de su posicionamiento y tamaño:
#property description "El panel cambia su transparencia al desplazarse," #property description " pero en este caso, además, el botón añadido sigue con su color" #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
Asimismo, para tener la posibilidad de trabajar con el botón, debemos declarar el objeto de la clase CButton:
//+------------------------------------------------------------------+ //| Class CLivePanelAndButton | //| Usage: main dialog of the Controls application | //+------------------------------------------------------------------+ class CLivePanelAndButton : public CAppDialog { private: CButton m_button1; // the button object public: CLivePanelAndButton(void);
y el procedimiento de creación del botón:
virtual bool OnDialogDragEnd(void); protected: //--- create dependent controls bool CreateButton1(void); };
En cuanto al código "CreateButton1", después de crear el botón, no debemos olvidarnos de añadir el botón al panel creado:
//+------------------------------------------------------------------+ //| 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); }
Aquí tenemos el resultado, si añadimos el botón al panel del ejemplo de arriba:
Como podemos ver, al moverse, el panel se vuelve transparente, pero el control agregado (es decir, el botón), permanece opaco. Aquí hay varias opciones para escribir el código según su gusto: hay quien solo necesitará que el panel del fondo sea transparente cuando se mueva, y hay quien querrá eso y que además el botón se vuelva transparente al mismo tiempo. Trabajaremos con la segunda opción: hagamos que el botón sea transparente cuando mueva el panel.
Cómo hacer que el botón también sea transparente al desplazar el panel
Vamos a implementarlo con la ayuda del código "Live panel and transparent Button.mq5".
Cuando se añade al panel un elemento de control simple o compuesto, el objeto que lo crea se añade al objeto m_client_area (recordemos que el objeto m_client_area se declara en la clase CDialog). Por consiguiente, al detectar el desplazamiento del panel, debemos organizar la iteración en el ciclo por todos los objetos añadidos a m_client_area. Es cómodo hacer esto en el primer procesador "OnDialogDragStart" (comienzo del desplazamiento del panel):
//+------------------------------------------------------------------+ //| 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()); }
Al detectar por primera vez el desplazamiento, tanto el panel como el botón se vuelven transparentes.
Nuestro segundo procesador "OnDialogDragProcess" (continuación del desplazamiento del panel) no lo vamos a cambiar. Y el tercero, "OnDialogDragEnd" (finalización del desplazamiento del panel), lo vamos a modificar, ya que debemos retornar el color para el botón:
//+------------------------------------------------------------------+ //| 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()); }
Ahora, en el código "Live panel and transparent Button.mq5" se ha implementado por completo el cambio de color del panel y el botón al desplazar el panel:
Añadimos dos botones al panel: controlamos el color de fondo del panel y el color del encabezado
Este ejemplo se contine en el código "Live panel and button Clicks.mq5", y ha sido creado usando como base el código anterior "Live panel and transparent Button.mq5". Solo que ahora, en lugar de los eventos de movimiento del panel, "capturaremos" los eventos de clic en los botones. Igualmente, en el panel habrá dos botones: un botón será responsable de cambiar el color del fondo del panel, y el segundo se encargará de cambiar el color de su encabezado.
Para detectar los eventos asociados a los controles agregados al panel, debemos declarar el procesador de eventosy escribir el propio controlador:
//+------------------------------------------------------------------+ //| 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 | //+------------------------------------------------------------------+
El procesador no es otra cosa que el método OnEvent, anotado con las macros del bloque "Events" y del bloque "Macro of event handling map" del archivo "Defines.mqh" (leer más información en el artículo Cómo crear un panel gráfico de cualquier nivel de complejidad).
El procesador se lee así:
- el evento OnEvent para la clase CLivePaneButtonClicks:
- si hay un clic en el elemento de control "m_button1", llamamos al procesador "OnClickButton1"
- si hay un clic en el elemento de control "m_button2", llamamos al procesador "OnClickButton2"
- retorno del evento OnEvent para la clase padre CAppDialog
Los controladores "OnClickButton1" y "OnClickButton2"
En ambos procesadores de eventos se ha organizado la iteración por todos los objetos de los que consta el panel (estos se enumeran en el punto Cómo se hace) en este caso, por todos los objetos del panel objeto "ExtDialog". Como resultado, se llama el método CWndContainer::ControlsTotal()
//+------------------------------------------------------------------+ //| 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; } } }
En el procesador "OnClickButton1" buscamos el objeto-área de cliente con el nombre prefix+"Client" (se tratará de un objeto de la clase CWndClient), mientras que en el procesador "OnClickButton2" será el objeto-encabezado con el nombre prefix+"Caption" (se tratará de un objeto de la clase CEdit). En ambos casos, seleccionamos aleatoriamente el color de fondo para los objetos encontrados. Así se ve el resultado:
Heredamos de CAppDialog
El esquema de implementación se distingue del aplicado en los ejemplos de la biblioteca estándar (\MQL5\Experts\Examples\Controls\ y \MQL5\Indicators\Examples\Panels\SimplePanel\), y precisamente en lo siguiente: en el archivo de inclusión "MyAppDialog.mqh" se crea la clase "CMyAppDialog", heredada de CAppDialog. En la clase se implementan solo los tres métodos de control del color de la forma y el encabezado. En ella no hay métodos para crear elementos de control añadidos, el procesador OnEvent y los procesadores de clic para los botones (controles agregados).
Los objetos de la clase CButton (se han añadido elementos de control, dos botones) se crean en el archivo de inicio "MyAppWindow.mq5". Asimismo, en el archivo "MyAppWindow.mq5", en el procesador OnChartEvent, se depuran los eventos de clic en los botones y se llaman con los métodos de cambio de color.
En nuestra clase, agregamos tres métodos:
- CMyAppDialog::ColorBackground — establecer el color del fondo,
- void CMyAppDialog::ColorCaption — establecer el color del encabezado,
- color CMyAppDialog::ColorCaption — obtener el color del encabezado.
El algoritmo para acceder a las propiedades de los objetos es análogo a los códigos anteriores: en el ciclo, se itera por todos los objetos que componen el panel y se comparan los nombres del objeto. Falta un método más: obtener el color de fondo. Pero esto no puede hacerse con medios simples.
//+------------------------------------------------------------------+ //| 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 "Clase CMyAppDialog, heredada de CAppDialog" #property description "Añadidos los métodos para establecer el color del fondo y el encabezado" #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 y destructor public: CMyAppDialog(void){}; ~CMyAppDialog(void){}; }; //+------------------------------------------------------------------+ //| Establece el color del fondo | //+------------------------------------------------------------------+ 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; } } //--- } //+------------------------------------------------------------------+ //| Establece el color del encabezado | //+------------------------------------------------------------------+ 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; } } //--- } //+------------------------------------------------------------------+ //| Obtiene el color del encabezado | //+------------------------------------------------------------------+ 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; } } //--- retornamos el color return clr; } //+------------------------------------------------------------------+
"MyAppWindow.mq5" — archivo de inicio. En este archivo se declaran las macros XRGB y GETRGB para generar el color. En OnInit se crea el panel, se añaden los botones y se inicia el funcionamiento del propio panel.
//+------------------------------------------------------------------+ //| 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 "Aplicación MyAppWindow basada en la clase CMyAppDialog" #property description "Añadidos los botones para establecer el color del fondo y el encabezado" #include "MyAppDialog.mqh" #include <Controls\Button.mqh> //--- macros para trabajar con el 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 { //--- primero procesamos los eventos de los botones 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()); } //--- ahora procesamos los demás eventos con el método de clase CMyAppDialog 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); } //+------------------------------------------------------------------+ //| Obtiene el color de forma aleatoria | //+------------------------------------------------------------------+ color GetRandomColor() { color clr=(color)GETRGB(XRGB(rand()%255,rand()%255,rand()%255)); return clr; } //+------------------------------------------------------------------+
Asimismo, en el archivo principal se encuentra el procesador de eventos OnChartEvent. Este reenvía todos los eventos al panel, pero al detectar el evento de clic de uno de los botones (CHARTEVENT_OBJECT_CLICK) llama los métodos de clase del panel (AppWindow.ColorBackground o AppWindow.ColorCaption).
Así funciona la conexión de los dos archivos: el mq5 principal iniciable y el archivo mqh de inclusión, en el que se encuentra la clase del panel.
Heredamos de CWndClient
En este ejemplo analizaremos la herencia de CWndClient: crearemos un objeto de la clase "CWndClient". Este objeto contendrá dicha funcionalidad:
- la creación del objeto "CMyWndClient" — área de cliente del panel;
- la creación del objeto de adición de dos botones al área de cliente;
- el procesador de clics sobre los botones añadidos (cambio de color del fondo del área de cliente y el color del encabezado del panel);
- además, para el área de cliente se activará el desplazmaiento horizontal (recuerde que la clase CWndClient supone un elemento combinado de control "Área de cliente" y es una clase básica para crear áreas con franjas de scrolling);
- por consiguiente, serán también procesadores de clics de desplazamiento horizontal (desplazamiento de los botones añadidos por el área de cliente).
Vamos a detenernos con más detalle en los archivos MyWndClient.mq5 y MyWndClient.mqh.
La diferencia respecto a los ejemplos de la biblioteca estándar (\MQL5\Experts\Examples\Controls\ y \MQL5\Indicators\Examples\Panels\SimplePanel\) consiste en que en el archivo de inclusión se encuentra la clase heredada de la clase CWndClient 10, el área de cliente. El ciclo completo de creación de un panel tiene el aspecto siguiente.
- Se crea el panel (el objeto AppWindow de la clase CAppDialog llama el método Create).
- Se crea nuestra área de cliente (el objeto ClientArea de la clase CMyWndClient, del archivo de inclusión MyWndClient.mqh llama el método Create).
- El área de cliente creada se añade al panel (en esencia, se encuentra sobre el área de cliente del 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); //--- establecemos el propietario ClientArea.SetOwner(GetPointer(AppWindow)); //--- ocultar lo invisible ClientArea.HideInvisble(HideInvisble); //--- run application AppWindow.Run(); //--- succeed return(INIT_SUCCEEDED); }
En el archivo de inclusión hay dos parámetros de entrada:
- anchura del panel — crear un panel de anchura normal o un panel ancho;
- ocultar lo invisible — representar u ocultar los elementos de control ocultos.
Al crear el panel, se tiene en cuenta solo un parámetro de entrada, la "anchura del panel". Así creamos el panel de anchura normal:
//+------------------------------------------------------------------+ //| 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
aquí tenemos un panel ancho:
//+------------------------------------------------------------------+ //| 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
El código de creación se diferencia solo en la anchura indicada (360 y 420); al crear los dos botones, esta anchura no se tiene en cuenta. Compare las dos últimas imágenes. Ahora, colóquelas una sobre otra:
Vemos que el botón "Caption" está ligeramente fuera del borde del panel de anchura normal, más allá del borde del área de cliente. Cuando el elemento de control agregado sale más allá de los límites, se oculta del usuario a la fuerza (pero no se elimina ni se destruye). El procedimiento para determinar si se oculta el elemento de control comienza al agregar este al área de cliente, al llamar el método CWndContainer::Add. En nuestro ejemplo, el método Add se llama en 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))); }
y sucesivamente se llama lo más importante: precisamente aquí se define si ocultar o no el elemento de control
//+------------------------------------------------------------------+ //| 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)); }
La configuración de la visibilidad del objeto se crea SOLO EN EL MOMENTO EN QUE SE AGREGA el elemento de control al área de cliente. Este es precisamente el inconveniente que puede aparecer después de minimizar y desplegar el panel.
Ejemplo: ambos parámetros de entrada se establecen como "false", después minimizamos y desplegamos el panel. Como resultado, después de crear el panel, se crea el botón "Caption", pero se oculta visualmente (ya que el botón está fuera del área del cliente, se oculta al agregarse al área del cliente). Pero una vez minimizado y desplegado el panel, la visibilidad de los elementos añadidos ya no se verifica y, por lo tanto, el botón Caption estará visible:
En este archivo se encuentra una clase heredada de la clase CWndClient, el área de cliente. En dicho archivo se contiene toda la funcionalidad:
- para crear nuestra área de cliente,
- para crear y añadir elementos de control,
- para procesar el evento de despliegue del panel
- para procesar los eventos de clic en la barra de desplazamiento horizontal
- para procesar eventos de clic en los botones: cambio del color del fondo del área de cliente y el color del encabezado.
Desplazamiento horizontal y elementos de control ocultos
Puesto que la clase del panel es heredada de la clase CWndClient, y la clase CWndClient constituye el elemento combinado de control "Área de cliente" y es una clase básica para crear áreas con barras de desplazamiento, incluimos en nuestra área de cliente una barra de desplazamiento vertical:
//+------------------------------------------------------------------+ //| Crear el 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())
Para que se pueda mover la barra de desplazamiento, indicamos su número de gradación: el valor de la posición máxima, y colocamos instantáneamente la barra de posicionamiento en su posición más a la derecha.
El desplazamiento horizontal también se usa para depurar el evento de despliegue del panel, para ello, "captamos" el evento ON_SHOW para el objeto m_scroll_h y llamamos el procesador OnShowScrollH:
//+------------------------------------------------------------------+ //| 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 no es otra cosa que el método OnEvent, anotado por macros desde el bloque "Events" y desde el bloque "Macro of event handling map" del archivo"Defines.mqh" (leer más información en el artículo Cómo crear un panel gráfico de cualquier nivel de complejidad).
En el procesador OnShowScrollH comprobamos el valor de la bandera interior m_hide_invisble (esta bandera adopta el valor del parámtro de entrada "ocultar lo invisible" del archivo MyWndClient.mq5 a través del método CMyWndClient::HideInvisble), y si no es necesario ocultar los elementos, simplemente salimos del procedimiento:
//+------------------------------------------------------------------+ //| Ha aparecido el desplazamiento ocultamos/representamos | //+------------------------------------------------------------------+ 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(); } } }
Si desea ocultar los elementos invisibles, buscamos los objetos que contengan en su nombre "Button" en un ciclo por todos los objetos del área del cliente, y comprobamos/asignamos forzosamente la visibilidad.
Clics de desplazamiento horizontal
Para los clics en el desplazamiento horizontal, estableceremos la posibilidad de mover los dos botones añadidos en nuestra área de cliente. Para ello, redefinimos los procesadores OnScrollLineRight y OnScrollLineLeft: los procesadores de los clics de los bonotes de desplazamiento horizontal. Si ha habido un clic en el botón derecho de desplazamiento horizontal, movemos los botones (método ShiftButton) en el salto m_scroll_size. Si ha habido un clic en el botón izquierdo, movemos los botones en un salto "-m_scroll_size", es decir, establecemos un desplazamiento negativo:
//+------------------------------------------------------------------+ //| 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); }En el método CMyWndClient::ShiftButton obtenemos el objeto con las coordenadas del botón, establecemos el desplazamiento de las coordenadas, desplazamos el botón y comprobamos/configuramos forzosamente la visibilidad del mismo:
//+-------------------------------------------------------------------------------+ //| Desplazamiento del botón a la izquierda o la derecha (dependiendo de shift) | //+-------------------------------------------------------------------------------+ bool CMyWndClient::ShiftButton(CButton *button,const int shift) { Print(__FUNCTION__); //--- desplazamos el botón CRect rect=button.Rect(); rect.Move(rect.left+shift,rect.top); button.Move(rect.left,rect.top); button.Visible(Contains(GetPointer(button))); return(true); }
Este es el aspecto que tiene (no olvide poner el parámetro "ocultar lo invisible" en el valor true):
Nuevos proyectos. ¿Cómo pueden ayudarnos en el estudio de los paneles?
Para escribir algo, siempre hay que estudiar el código. En el caso de la creación de paneles, estudiar las clases puede consumir mucho tiempo. Esto se debe principalmente al hecho de que no hay una representación visual de la estructura de las clases. Y al mismo tiempo es muy difícil entender qué clases de la biblioteca estándar han estado involucradas en la creación de los paneles.
Por fortuna, hace poco se presentaron los Nuevos Proyectos en el editor MetaEditor.
Un proyecto es un archivo aparte con la extensión "MQPROJ" que almacena la configuración del programa, los parámetros de compilación y la información sobre todos los archivos utilizados. Para trabajar cómodamente con el proyecto, disponemos de una pestaña aparte en el navegador. En ella se muestran por categorías todos los archivos utilizados: de inclusión, de recursos, de encabezado, etcétera.
Echemos un vistazo a la descripción de la pestaña creada por separado. "En ella se muestran por categorías todos los archivos utilizados: de inclusión, de recursos, encabezado, etcétera."! ¡Y esto es justo lo que necesitamos!
Vamos a tratar de crear un proyecto a partir del último archivo "Live panel and button Clicks.mq5". Para lograrlo, hacemos clic con el botón derecho en el archivo "Live panel and button Clicks.mq5" y en el menú desplegable seleccionamos el punto "Nuevo proyecto del archivo fuente":
Como resultado, se creará un nuevo proyecto, y en la ventana "Navegador" se abrirá la pestaña "Proyecto", en la que podremos ver todos los archivos utilizados:
y "Wnd.mqh" (en ella está la clase CWnd), y "Dialog.mqh (en ella están las clases CDialog y CAppDialog), y Button.mhq (clase CButton). Desde esta pestaña, es conveniente ir a la clase requerida. Es mucho más cómodo que navegar a través de las pestañas del editor MetaEditor. Por ejemplo, tengo un pequeño "zoológico" con una amplia variedad de archivos. Ir desde allí, por ejemplo, a Dialog.mqh a través de la búsqueda en las pestañas, es problemático:
Conclusión
El artículo muestra una forma bastante inusual de acceder a las propiedades "Color de fondo", "Color del marco" y "Color del encabezado" para los elementos del panel. Nosotros no habíamos encontrado este método antes. El truco radica en saber que todos los elementos del panel se heredan de la clase padre CWnd, por lo que el objeto (y por lo tanto la clase del panel creado), es un contenedor para todos los elementos de control. Esto implica que podemos recorrer todos los controles y obtener/establecer las propiedades necesarias.
Nombre del archivo | Comentarios |
---|---|
Live panel.mq5 | Panel sin elementos de control agregados. Al arrastrarlo se vuelve transparente |
Live panel and Button.mq5 | Panel del botón añadido. Al arrastrar el panel se vuelve transparente, y el botón permanece con su color |
Live panel and transparent Button.mq5 | Panel del botón añadido. Durante el arrastre, tanto el panel como el botón se vuelven transparentes |
Live panel and button Clicks.mq5 | Panel con dos botones. Procesamiento de clics en los botones: se genera un color de fondo para el panel y el encabezado |
MyAppWindow.mq5 y MyAppDialog.mqh | Ejemplo de creación del panel mediante herencia desde CAppDialog |
MyWndClient.mq5 y MyWndClient.mqh | Ejemplo de creación de un panel heredando de CWndClient: de la clase del área de cliente |
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/4575
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso