Interfaces gráficas V: Control "Lista combinada" (Capítulo 3)
Anatoli Kazharski | 24 junio, 2016
Índice
- Introducción
- Control “Lista combinada”
- Desarrollo de la clase para la creación del control
- Métodos para procesar los eventos del control
- Conexión de la clase del control con el motor de librería
- Prueba del control en la interfaz gráfica de la aplicación
- Conclusión
Introducción
El primer artículo de la serie nos cuenta con más detalles para qué sirve esta librería: Interfaces gráficas I: Preparación de la estructura de la librería (Capítulo 1). Al final de cada artículo de la serie se muestra la lista completa de los capítulos con los enlaces. Además, se puede descargar la versión completa de la librería en la fase actual del desarrollo del proyecto. Es necesario colocar los ficheros en los mismos directorios, tal como están ubicados en el archivo.
En dos primeros capítulos de la quinta parte sobre las interfaces gráficas hemos desarrollado las clases para crear la barra de desplazamiento y la lista. Ahí se mostraba cómo incluir la barra de desplazamiento en un control en el que los datos no caben en el área especificada. En este capítulo vamos a hablar de la clase para la creación del control llamado “Lista combinada”. Éste también es un control compuesto que incluye los controles analizados en dos primeros capítulos de la quinta parte.
Control “Lista combinada”
La lista combinada o combobox es un control compuesto cuyas partes principales son (1) el botón y (2) la lista. En este caso, la lista es un control desplegable que se abre con un clic en el botón. Después de la selección de un elemento de la lista, su texto se muestra en el botón, mientras que la lista se cierra. Cuando en un programa hay varios parámetros de diversas opciones, los combobox vendrán muy a propósito, ya que permiten crear una interfaz gráfica compacta.
Abajo se muestran los objetos primitivos para componer el control “Combobox”.
- Fondo del control
- Etiqueta (descripción del control)
- Botón
- Indicio de lista desplegable
Fig. 1. Partes integrantes del control “Combobox”.
A continuación, vamos a analizar el desarrollo de la clase para la creación de este control.
Desarrollo de la clase para la creación del control
Analizaremos paso a paso todas las fases del desarrollo del control “Combobox” para poder utilizar luego este artículo como ejemplo de creación de propias clases de este tipo. Primero hay que crear el archivo mqh (ComboBox.mqh) e incluir en él todos los archivos necesarios con las clases que serán necesarias para la creación de la lista combinada. En este caso, serán tres archivos con las siguientes clases:
- CElement — clase base para la creación del control.
- CWindow — clase del formulario al que va a adjuntarse el control.
- CListView — clase de la lista cuya visibilidad va a manejarse por Combobox.
//+------------------------------------------------------------------+ //| ComboBox.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" #include "ListView.mqh"
Luego, en el archivo ComboBox.mqh hay que crear la clase CComboBox y los métodos estándar para cada control de la librería:
//+------------------------------------------------------------------+ //| Clase para crear la lista combinada | //+------------------------------------------------------------------+ class CComboBox : public CElement { private: //--- Puntero al formulario al que está adjuntado el control CWindow *m_wnd; //--- public: CComboBox(void); ~CComboBox(void); //--- Guarda el puntero del formulario void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } //--- public: //--- Manejador de eventos del gráfico virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- Temporizador virtual void OnEventTimer(void); //--- Desplazamiento del control virtual void Moving(const int x,const int y); //--- (1) Mostrar, (2) ocultar, (3) resetear, (4) eliminar virtual void Show(void); virtual void Hide(void); virtual void Reset(void); virtual void Delete(void); //--- (1) Establecer, (2) resetear las prioridades para el clic izquierdo del ratón virtual void SetZorders(void); virtual void ResetZorders(void); //--- Resetear color virtual void ResetColors(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CComboBox::CComboBox(void) { //--- Guardamos el nombre de la clase del control en la clase base CElement::ClassName(CLASS_NAME); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CComboBox::~CComboBox(void) { }
Al usuario hay que darle la posibilidad de configurar su propio esquema de colores de la interfaz gráfica. Para eso hay que proporcionar el acceso a los ajustes de las propiedades de objetos de los se compone el control. Abajo se muestran todas las propiedades que se puede ajustar antes de la creación del control:
- Color del fondo del control
- Descripción del Combobox (etiqueta de texto)
- Márgenes para la etiqueta de texto en los ejes X y Y
- Colores de las etiquetas de texto en diferentes estados
- Texto del botón (texto del elemento seleccionado en la lista)
- Tamaño del botón
- Colores del botón en diferentes estados
- Colores del marco del botón en diferentes estados
- Colores del texto del botón en diferentes estados
- Imágenes para la flecha como indicio de la lista desplegable para el modo activo y bloqueado
- Márgenes para las imágenes de la flecha en los ejes X y Y
El código de abajo contiene los campos y los métodos para establecer las propiedades mencionadas:
class CComboBox : public CElement { private: //--- Propiedades de Combobox // Color del fondo general color m_area_color; //--- Texto y márgenes de la etiqueta de texto string m_label_text; int m_label_x_gap; int m_label_y_gap; //--- Colores de la etiqueta de texto en diferentes estados color m_label_color; color m_label_color_off; color m_label_color_hover; color m_label_color_array[]; //--- (1) Texto del botón y (2) sus tamaños string m_button_text; int m_button_x_size; int m_button_y_size; //--- Colores del botón en diferentes estados color m_button_color; color m_button_color_off; color m_button_color_hover; color m_button_color_pressed; color m_button_color_array[]; //--- Colores del marco del botón en diferentes estados color m_button_border_color; color m_button_border_color_off; //--- Color del texto del botón en diferentes estados color m_button_text_color; color m_button_text_color_off; //--- Márgenes del icono int m_drop_arrow_x_gap; int m_drop_arrow_y_gap; //Iconos del botón con el menú desplegable en el estado activo y bloqueado string m_drop_arrow_file_on; string m_drop_arrow_file_off; //--- Prioridades para el clic izquierdo del ratón int m_area_zorder; int m_button_zorder; int m_zorder; //--- public: //--- (1) Color del fondo, (2) establece y (3) devuelve el valor de la etiqueta de texto void AreaColor(const color clr) { m_area_color=clr; } void LabelText(const string label_text) { m_label_text=label_text; } string LabelText(void) const { return(m_label_text); } //--- Márgenes de la etiqueta de texto void LabelXGap(const int x_gap) { m_label_x_gap=x_gap; } void LabelYGap(const int y_gap) { m_label_y_gap=y_gap; } //--- (1) Devuelve el texto del botón, (2) establecer los tamaños del botón string ButtonText(void) const { return(m_button_text); } void ButtonXSize(const int x_size) { m_button_x_size=x_size; } void ButtonYSize(const int y_size) { m_button_y_size=y_size; } //--- (1) Color del fondo, (2) colores de la etiqueta de texto void LabelColor(const color clr) { m_label_color=clr; } void LabelColorOff(const color clr) { m_label_color_off=clr; } void LabelColorHover(const color clr) { m_label_color_hover=clr; } //--- Colores del botón void ButtonBackColor(const color clr) { m_button_color=clr; } void ButtonBackColorOff(const color clr) { m_button_color_off=clr; } void ButtonBackColorHover(const color clr) { m_button_color_hover=clr; } void ButtonBackColorPressed(const color clr) { m_button_color_pressed=clr; } //--- Colores del marco del botón void ButtonBorderColor(const color clr) { m_button_border_color=clr; } void ButtonBorderColorOff(const color clr) { m_button_border_color_off=clr; } //--- Colores del texto del botón void ButtonTextColor(const color clr) { m_button_text_color=clr; } void ButtonTextColorOff(const color clr) { m_button_text_color_off=clr; } //--- Establecer los iconos para el botón con el menú desplegable en el estado activo y bloqueado void DropArrowFileOn(const string file_path) { m_drop_arrow_file_on=file_path; } void DropArrowFileOff(const string file_path) { m_drop_arrow_file_off=file_path; } //--- Márgenes del icono void DropArrowXGap(const int x_gap) { m_drop_arrow_x_gap=x_gap; } void DropArrowYGap(const int y_gap) { m_drop_arrow_y_gap=y_gap; } };
Los valores predefinidos de las propiedades mencionadas se inicializan en el constructor de la clase:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CComboBox::CComboBox(void) : m_area_color(C'15,15,15'), m_label_text("combobox: "), m_label_x_gap(0), m_label_y_gap(2), m_label_color(clrWhite), m_label_color_off(clrGray), m_label_color_hover(C'85,170,255'), m_button_text(""), m_button_y_size(18), m_button_text_color(clrBlack), m_button_text_color_off(clrDarkGray), m_button_color(clrGainsboro), m_button_color_off(clrLightGray), m_button_color_hover(C'193,218,255'), m_button_color_pressed(C'153,178,215'), m_button_border_color(clrWhite), m_button_border_color_off(clrWhite), m_drop_arrow_x_gap(16), m_drop_arrow_y_gap(1), m_drop_arrow_file_on(""), m_drop_arrow_file_off("") { //--- Establecemos las prioridades para el clic izquierdo del ratón m_zorder =0; m_area_zorder =1; m_button_zorder =2; }
El combobox se crea a través de cinco métodos privados que van a llamarse en en el método público principal CComboBox::CreateComboBox(). Para acceder a los ajustes de las propiedades de la lista y su barra de desplazamiento, vamos a crear los métodos para obtener los punteros a estos controles:
class CComboBox : public CElement { private: //--- Objetos para crear el combobox CRectLabel m_area; CLabel m_label; CEdit m_button; CBmpLabel m_drop_arrow; CListView m_listview; //--- public: //--- Métodos para crear el combobox bool CreateComboBox(const long chart_id,const int subwin,const int x,const int y); //--- private: bool CreateArea(void); bool CreateLabel(void); bool CreateButton(void); bool CreateDropArrow(void); bool CreateList(void); //--- public: //--- Devuelve los punteros a (1) la lista y a (2) la barra de desplazamiento CListView *GetListViewPointer(void) { return(::GetPointer(m_listview)); } CScrollV *GetScrollVPointer(void) { return(m_listview.GetScrollVPointer()); } };
De los métodos que figuran en el código de arriba, hablaremos más detalladamente sólo del método para la creación de la lista CComboBox::CreateList(). Los demás no tienen nada en particular de lo que no hemos hablado antes en los artículos anteriores de esta parte. Pero antes eso, hay que introducir algunas adiciones en la clase CListView.
Cuando una lista es desplegable, eso significa que forma parte de otro control, y en su manejador de eventos puede surgir la necesidad de realizar el seguimiento del foco sobre este control al que está adjuntado. En este caso, se trata del Combobox. Añadimos el campo y el método a la clase CListView para guardar el puntero al combobox al que va a adjuntarse la lista.
class CListView : public CElement { private: //--- Puntero al control que gestiona la visibilidad de la lista CElement *m_combobox; //--- public: //--- Guarda el puntero al combobox void ComboBoxPointer(CElement &object) { m_combobox=::GetPointer(object); } };
Si la lista es desplegable, en el método principal (público) de la creación de la lista, agregamos la comprobación de la presencia del puntero. Si durante la creación de la lista resulta que no hay puntero, la creación de la interfaz gráfica será interrumpida, y en el registro aparecerá el mensaje correspondiente.
Abajo se muestra la versión reducida del método CListView::CreateListView():
//+------------------------------------------------------------------+ //| Crea la lista | //+------------------------------------------------------------------+ bool CListView::CreateListView(const long chart_id,const int window,const int x,const int y) { //--- Salir si no hay puntero al formulario //--- Si la lista es desplegable, significa que hace falta un puntero al combobox al que será adjuntado if(CElement::IsDropdown()) { //--- Salir si no hay puntero al combobox if(::CheckPointer(m_combobox)==POINTER_INVALID) { :: Print(__FUNCTION__," > Antes de crear la lista desplegable, hay que pasar a la clase " "puntero al combobox: CListView::ComboBoxPointer(CElement &object)"); return(false); } } //--- Inicialización de variables //--- Márgenes desde el punto extremo //--- Crear el botón //--- Ocultar el elemento si es la ventana de diálogo o está minimizada //--- return(true); }
Ahora volveremos al desarrollo de la clase de la lista combinada (CComboBox). La propiedad que indica que la lista va a ser desplegable tiene que establecerse en la fase inicial, o sea en el constructor de la clase:
CComboBox::CComboBox(void) { //--- Modo de la lista desplegable m_listview.IsDropdown(true); }
Durante la creación de la lista, en el principio del método hay que guardar los punteros al formulario y al combobox a los que va a adjuntarse la lista. No olvidemos que el identificador de la lista y del combobox tiene que ser común, porque en este caso es el mismo control. Hya que ocultar la lista después de su creación.
//+------------------------------------------------------------------+ //| Crea la lista | //+------------------------------------------------------------------+ bool CComboBox::CreateList(void) { //--- Guardamos los punteros al formulario y al combobox m_listview.WindowPointer(m_wnd); m_listview.ComboBoxPointer(this); //--- Coordenadas int x=CElement::X2()-m_button_x_size; int y=CElement::Y()+m_button_y_size; //--- Establecemos las propiedades m_listview.Id(CElement::Id()); m_listview.XSize(m_button_x_size); //--- Creamos el control if(!m_listview.CreateListView(m_chart_id,m_subwin,x,y)) return(false); //--- Ocultar lista m_listview.Hide(); return(true); }
Para establecer el número de elementos en la lista y llenarla con valores, vamos a añadir los métodos correspondientes a la clase CComboBox :
class CListView : public CElement { public: //--- Establecer (1) el tamaño de la lista (número de elementos) y (2) de su parte visible void ItemsTotal(const int items_total) { m_listview.ListSize(items_total); } void VisibleItemsTotal(const int visible_items_total) { m_listview.VisibleListSize(visible_items_total); } //--- Guarda el valor devuelto en la lista según el índice especificado void ValueToList(const int item_index,const string item_text); }; //+------------------------------------------------------------------+ //| Guarda el valor devuelto en la lista según el índice especificado | //+------------------------------------------------------------------+ void CComboBox::ValueToList(const int item_index,const string item_text) { m_listview.ValueToList(item_index,item_text); }
Para seleccionar (resaltar) un elemento en la lista, vamos a crear el método CComboBox::SelectedItemByIndex(). El índice del elemento a seleccionar es el único argumento que tiene que ser pasado en este método. Luego, el resalto del elemento se hace en el método homónimo de la lista (CListView), en el que el índice se ajusta si el diapasón se excede. Después de eso, el texto del elemento se guarda y se inserta en el botón del combobox.
class CListView : public CElement { public: //--- Resaltar el elemento según el índice especificado void SelectedItemByIndex(const int index); }; //+------------------------------------------------------------------+ //| Resaltar el elemento según el índice especificado | //+------------------------------------------------------------------+ void CComboBox::SelectedItemByIndex(const int index) { //--- Seleccionar el elemento de la lista m_listview.SelectedItemByIndex(index); //--- Guardar e insertar el texto en el botón m_button_text=m_listview.SelectedItemText(); m_button.Description(m_listview.SelectedItemText()); }
Vamos a necesitar un método para bloquear y desbloquear el control, así como para obtener su estado actual. Dependiendo del nuevo estado del control, sus objetos reciben el color correspondiente a este estado. Más tarde mostraremos estos ejemplos.
class CListView : public CElement { public: //--- Obtener y establecer el estado del control bool ComboBoxState(void) const { return(m_combobox_state); } void ComboBoxState(const bool state); }; //+------------------------------------------------------------------+ //| Cambio del estado del combobox | //+------------------------------------------------------------------+ void CComboBox::ComboBoxState(const bool state) { m_combobox_state=state; //--- Establecer los colores para los objetos de acuerdo con el estado actual m_label.Color((state)? m_label_color : m_label_color_off); m_button.Color((state)? m_button_text_color : m_button_text_color_off); m_button.BackColor((state)? m_button_color : m_button_color_off); m_button.BorderColor((state)? m_button_border_color : m_button_border_color_off); m_drop_arrow.State(state); }
Cuando el cursor se sitúa sobre los objetos del control, el cambio de sus colores, usando el método CComboBox::ChangeObjectsColor(), va a realizarse sólo cuando el control está disponible:
class CListView : public CElement { public: //--- Cambio del color del objeto al situar el cursor sobre él void ChangeObjectsColor(void); }; //+------------------------------------------------------------------+ //| Cambio del color del objeto al situar el cursor sobre él | //+------------------------------------------------------------------+ void CComboBox::ChangeObjectsColor(void) { //--- Salir si el control está bloqueado if(!m_combobox_state) return; //--- Cambiamos el color de los objetos CElement::ChangeObjectColor(m_label.Name(),CElement::MouseFocus(),OBJPROP_COLOR,m_label_color,m_label_color_hover,m_label_color_array); CElement::ChangeObjectColor(m_button.Name(),CElement::MouseFocus(),OBJPROP_BGCOLOR,m_button_color,m_button_color_hover,m_button_color_array); }
La llamada al método CComboBox::ChangeObjectsColor() debe realizarse en el temporizador del control CComboBox::OnEventTimer(). Adelantándome un poco, quiero mencionar que un Combobox puede formar parte de un control más complicado y desplegable al mismo tiempo. En uno de los siguientes artículos, de ejemplo va a servir el “Calendario desplegable” que va a incluir el Combobox. Por eso, las condiciones para el cambio del color en el temporizador para este control van a formarse de la siguiente manera (véase el código de abajo):
- Si el control es desplegable y la lista está oculta.
- Si la primera condición no se cumple, comprobamos la disponibilidad del formulario y del propio control.
//+------------------------------------------------------------------+ //| Temporizador | //+------------------------------------------------------------------+ void CComboBox::OnEventTimer(void) { //--- Si el control es desplegable y la lista está oculta if(CElement::IsDropdown() && !m_listview.IsVisible()) ChangeObjectsColor(); else { //--- Si el formulario y el control no están bloqueados if(!m_wnd.IsLocked() && m_combobox_state) ChangeObjectsColor(); } }
Para manejar la visibilidad de la lista del combobox, vamos a crear el método CComboBox::ChangeComboboxListState(). Este método va a cambiar el estado actual del combobox por el contrario. En su principio va a estar la comprobación de la disponibilidad del control. Si el combobox está bloqueado, el programa saldrá del método. Luego, el código se divide en dos ramas:
- En caso si la lista ya se encuentra visible, pues aquí será ocultada, y para el botón de combobox se establecerán los colores correspondientes. Después de eso, si el control no es desplegable hay que desbloquear el formulario y resetear el identificador del control activo.
- Si la lista está oculta, hay que mostrarla y establecer el color correspondiente a este estado para el botón del combobox. Al final de la rama hay que bloquear el formulario y guardar el identificador del control activador.
class CListView : public CElement { public: //--- Cambia el estado actual del combobox por el contrario void ChangeComboBoxListState(void); }; //+------------------------------------------------------------------+ //| Cambia el estado actual del combobox por el contrario | //+------------------------------------------------------------------+ void CComboBox::ChangeComboBoxListState(void) { //--- Salir si el control está bloqueado if(!m_combobox_state) return; //--- Si la lista está visible if(m_listview.IsVisible()) { //--- Ocultar lista m_listview.Hide(); //--- Establecer los colores m_label.Color(m_label_color_hover); m_button.BackColor(m_button_color_hover); //--- Si el control no es desplegable if(!CElement::IsDropdown()) { //--- Desbloquear el formulario m_wnd.IsLocked(false); m_wnd.IdActivatedElement(WRONG_VALUE); } } //--- Si la lista está oculta else { //--- Mostrar la lista m_listview.Show(); //--- Establecer los colores m_label.Color(m_label_color_hover); m_button.BackColor(m_button_color_pressed); //--- Bloquear el formulario m_wnd.IsLocked(true); m_wnd.IdActivatedElement(CElement::Id()); } }
Métodos para procesar los eventos del control
Ahora podemos pasar a la configuración del manejador de eventos del control CComboBox::OnEvent(). Aquí vamos a necesitar dos métodos privados (private) adicionales:
- CComboBox::OnClickButton() – en este método va a realizar el seguimiento del clic en el botón del combobox.
- CComboBox::CheckPressedOverButton() – va a realizar aquí el seguimiento del estado del botón izquierdo sobre el botón del combobox.
class CListView : public CElement { private: //--- Procesamiento del clic en el botón bool OnClickButton(const string clicked_object); //--- Comprobando si el botón izquierdo del ratón está pulsado sobre el botón del combobox void CheckPressedOverButton(void); };
El código del método CComboBox::OnClickButton() es muy simple. Va contener sólo la comprobación del nombre del objeto que ha sido pulsado, y la llamada al método CComboBox::ChangeComboBoxListState() discutido anteriormente que debe encargarse de la visibilidad de la lista del combobox (véase el código de abajo).
//+------------------------------------------------------------------+ //| Clic en el botón del combobox | //+------------------------------------------------------------------+ bool CComboBox::OnClickButton(const string clicked_object) { //--- Salimos si el nombre del objeto no coincide if(clicked_object!=m_button.Name()) return(false); //--- Cambiar el estado de la lista ChangeComboboxListState(); return(true); }
Abajo se muestra el código del método CComboBox::CheckPressedOverButton(). En el principio del método se comprueban los formularios disponibles y el identificador del control activador. El programa sale del método si el formulario está bloqueado y los identificadores no coinciden.
Luego, si no hay foco sobre del control, comprobamos si ha foco sobre la lista, y el estado actual de la barra de desplazamiento. Si resulta que el foco está fuera de la lista o la barra de desplazamiento se encuentra en modo de desplazamiento del deslizador, el programa sale del método. Recordamos que después de que el deslizador haya sido pasado en el modo de desplazamiento, se puede desplazarlo incluso si el cursor sale fuera de los límites del deslizador. Si no se ha cumplido ninguna de estas condiciones, entonces:
(1) la lista se oculta.
(2) se recuperan los colores de los objetos del control,
y al final de este bloque del código, si los identificadores coinciden y el control no es desplegable, (3) hay que desbloquear el formulario. Recordaré que el formulario puede desbloquearse sólo por el control que lo ha bloqueado.
En caso cuando sobre el control hay foco, primero se realiza la comprobación de la visibilidad de la lista. Si la lista está visible, no hay sentido de continuar y el programa sale del método. Si la lista está oculta, entonces se establecen los colores correspondientes dependiendo del foco sobre el botón del combobox.
//+------------------------------------------------------------------+ //| Comprobando si el botón izquierdo del ratón está pulsado sobre el botón | //+------------------------------------------------------------------+ void CComboBox::CheckPressedOverButton(void) { //--- Salir si el formulario está bloqueado y los identificadores no coinciden if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id()) return; //--- Si no hay foco if(!CElement::MouseFocus()) { //--- Salir si el foco no está sobre la lista o la barra de desplazamiento está activada if(m_listview.MouseFocus() || m_listview.ScrollState()) return; //--- Ocultar lista m_listview.Hide(); //--- Recuperar los colores ResetColors(); //--- Si los identificadores coinciden y el control no es desplegable if(m_wnd.IdActivatedElement()==CElement::Id() && !CElement::IsDropdown()) //--- Desbloquear el formulario m_wnd.IsLocked(false); } //--- Si hay foco else { //--- Salir si la lista está visible if(m_listview.IsVisible()) return; //--- Establecer el color en función del foco if(m_button.MouseFocus()) m_button.BackColor(m_button_color_pressed); else m_button.BackColor(m_button_color_hover); } }
La llamada al método CComboBox::OnClickButton() debe ubicarse en el bloque de procesamiento del evento del clic en el objeto gráfico, que puede ser identificado por el identificador CHARTEVENT_OBJECT_CLICK:
//+------------------------------------------------------------------+ //| Manejador de eventos | //+------------------------------------------------------------------+ void CComboBox::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Procesamiento del evento del clic izquierdo en el objeto if(id==CHARTEVENT_OBJECT_CLICK) { //--- Clic en el botón del combobox if(OnClickButton(sparam)) return; } }
En el bloque del procesamiento del evento del desplazamiento del cursor (CHARTEVENT_MOUSE_MOVE) antes de llamar al método CComboBox::CheckPressedOverButton() hay que comprobar:
- visibilidad del control;
- disponibilidad del control;
- estado del botón izquierdo del ratón.
//+------------------------------------------------------------------+ //| Manejador de eventos | //+------------------------------------------------------------------+ void CComboBox::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Procesamiento del evento del desplazamiento del cursor if(id==CHARTEVENT_MOUSE_MOVE) { //--- Salir si el control está ocultado if(!CElement::IsVisible()) return; //--- Coordenadas int x=(int)lparam; int y=(int)dparam; //--- Comprobación del foco sobre los controles CElement::MouseFocus(x>CElement::X() && x<CElement::X2() && y>CElement::Y() && y<CElement::Y2()); m_button.MouseFocus(x>m_button.X() && x<m_button.X2() && y>m_button.Y() && y<m_button.Y2()); //--- Salir si el control está bloqueado if(!m_combobox_state) return; //--- Salir si el botón izquierdo del ratón está suelto if(sparam=="0") return; //--- Comprobación del botón izquierdo del ratón pulsado sobre el botón de división CheckPressedOverButton(); return; } }
En el momento cuando se pulsa uno de los elementos de la lista, se genera el evento de usuario ON_CLICK_LIST_ITEM (véase el código de abajo). Hay que recibir este mensaje en el manejador de eventos del combobox, y si los identificadores de los controles coinciden (es decir el mensaje ha llegado de la lista adjunta a este combobox), entonces guardamos el texto del elemento seleccionado de la lista en el botón, y ocultamos la lista usando el método CComboBox::ChangeComboBoxListState().
Para asegurar la conexión con la aplicación desarrollada, este mensaje con el identificador ON_CLICK_LIST_ITEM se puede recibir en la clase de personalizada CProgram. Pero también se puede enviar el mensaje desde el combobox con el (1) el identificador del evento único para él, (2) identificador del control y (3) descripción del combobox. Para ampliar las posibilidades de identificación de eventos de los controles, vamos a usar esta opción también. Vamos a añadir el identificador único para el control “Lista combinada” al archivo Defines.mqh.
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #define ON_CLICK_COMBOBOX_ITEM (17) // Selección del elemento en la lista del combobox
En este caso, en el manejador de eventos del control hay que añadir la línea marcada con el color azul en el código de abajo:
//+------------------------------------------------------------------+ //| Manejador de eventos | //+------------------------------------------------------------------+ void CComboBox::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Procesamiento del evento del clic en un elemento de la lista if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM) { //--- Si los identificadores coinciden if(lparam==CElement::Id()) { //--- Guardar e insertar el texto en el botón m_button_text=m_listview.SelectedItemText(); m_button.Description(m_listview.SelectedItemText()); //--- Cambiar el estado de la lista ChangeComboBoxListState(); //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_CLICK_COMBOBOX_ITEM,CElement::Id(),0,m_label_text); } //--- return; } }
Además, se puede configurar la ocultación de la lista cuando se cambian las propiedades del gráfico. Para eso hay que procesar el evento con el manejador CHARTEVENT_CHART_CHANGE (véase el código de abajo):
//+------------------------------------------------------------------+ //| Manejador de eventos | //+------------------------------------------------------------------+ void CComboBox::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Procesamiento del evento del cambio de propiedades del gráfico if(id==CHARTEVENT_CHART_CHANGE) { //--- Salir si el control está bloqueado if(!m_combobox_state) return; //--- Ocultar la lista m_listview.Hide(); //--- Recuperar los colores ResetColors(); //--- Desbloquear el formulario m_wnd.IsLocked(false); m_wnd.IdActivatedElement(WRONG_VALUE); return; } }
La clase para la creación del control “Lista combinada” ya está lista para las pruebas. Pero antes de eso, es necesario conectarla con el motor de la librería para que funcione correctamente.
Conexión de la clase del control con el motor de librería
Para conectar el control con el motor de la librería, es necesario realizar unos cuantos simples pasos:
1.Incluir el archivo con la clase del control en el archivo raíz de la librería WndContainer.mqh.
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "ComboBox.mqh"
2.Si es necesario, crear el array privado para el control y el método para obtener el tamaño de este array. En este caso, el array privado será necesario para las listas desplegables.
class CWndContainer { protected: //--- Estructura de los arrays de controles struct WindowElements { //--- Array común de todos los objetos //--- Array común de todos los controles //--- Arrays personales de controles: // Array de los menús contextuales //Array de los menús principales //--- Descripciones emergentes //--- Array de las listas desplegables de diferentes tipos CElement *m_drop_lists[]; }; //--- Array de los arrays de los controles para cada ventana WindowElements m_wnd[]; //--- public: //--- Número de listas desplegables int DropListsTotal(const int window_index); }; //+------------------------------------------------------------------+ //| Devuelve el número de listas desplegables según el índice especificado de la ventana | //+------------------------------------------------------------------+ int CWndContainer::DropListsTotal(const int window_index) { if(window_index>=::ArraySize(m_wnd)) { ::Print(PREVENTING_OUT_OF_RANGE); return(WRONG_VALUE); } //--- return(::ArraySize(m_wnd[window_index].m_drop_lists)); }
3.Crear el método privado para añadir los punteros al array privado. En este caso, hay que obtener el puntero a la lista desde el combobox y añadirlo al array privado. Además de eso, hay que añadir los punteros a los objetos de la lista y las barras de desplazamiento de esta lista al array común de los objetos. Para más detalles véase el código del método CWndContainer::AddComboBoxElements() en el código de abajo.
class CWndContainer { private: //--- Guarda los punteros a los controles del menú contextual en la base //--- Guarda los punteros a los controles del menú principal en la base //--- Guarda los punteros a los elementos del botón de división en la base //--- Guarda los punteros a los elementos de las descripciones emergentes en la base //--- Guarda los punteros a los objetos de la lista en la base //--- Guarda los punteros a los elementos de las listas desplegables en la base bool AddComboBoxElements(const int window_index,CElement &object); }; //+------------------------------------------------------------------+ //| Guarda el puntero a la lista desplegable en el array privado | //+------------------------------------------------------------------+ bool CWndContainer::AddComboBoxElements(const int window_index,CElement &object) { //--- Salimos si no es una descripción emergente if(object.ClassName()!="CComboBox") return(false); //--- Obtenemos el puntero al combobox CComboBox *cb=::GetPointer(object); //--- for(int i=0; i<2; i++) { //--- Aumentar el array de controles int size=::ArraySize(m_wnd[window_index].m_elements); ::ArrayResize(m_wnd[window_index].m_elements,size+1); //--- Añadimos la lista a la base if(i==0) { CListView *lv=cb.GetListViewPointer(); m_wnd[window_index].m_elements[size]=lv; AddToObjectsArray(window_index,lv); //--- Añadimos el puntero al array privado AddToRefArray(lv,m_wnd[window_index].m_drop_lists); } //--- Añadimos la barra de desplazamiento a la base else if(i==1) { CScrollV *sv=cb.GetScrollVPointer(); m_wnd[window_index].m_elements[size]=sv; AddToObjectsArray(window_index,sv); } } //--- return(true); }
4.Y finalmente, si las acciones del tercer punto eran necesarias, no olviden llamar al método CWndContainer::AddComboBoxElements() en el método principal CWndContainer::AddToElementsArray(), donde tienen que ubicarse las llamadas a estos métodos.
//+------------------------------------------------------------------+ //| Añade el puntero al array de controles | //+------------------------------------------------------------------+ void CWndContainer::AddToElementsArray(const int window_index,CElement &object) { //--- Si en la base no hay formularios para los controles //--- Si se solicita el formulario que no existe //--- Añadimos al array común de controles //--- Añadir los objetos del control al array común de objetos //--- Recordamos id del último control en todos los formularios //--- Aumentamos el contador de los identificadores de controles //--- Guarda los punteros a los objetos del menú contextual en la base //--- Guarda los punteros a los objetos del menú principal en la base //--- Guarda los punteros a los objetos del botón de división en la base //--- Guarda los punteros a los objetos de las descripciones emergentes en la base //--- Guarda los punteros a los objetos de la lista en la base //--- Guarda los punteros a los objetos del combobox en la base if(AddComboBoxElements(window_index,object)) return; }
Cuando el control es desplegable, durante su uso pueden surgir las situaciones (depende de la posición de este control en el formulario) cuando su área sale fuera los límites del formulario. Para evitar el desplazamiento del gráfico cuando el botón izquierdo del ratón se pulsa sobre el control desplegable, siempre hay que controlar la posición del cursor y desactivar el desplazamiento del gráfico si éste se encuentra sobre uno de estos controles. Para estos propósitos ya hemos escrito el método CWndEvents::SetChartState() en la clase CWndEvents. Ahora hay que completarlo con la comprobación de las listas desplegables. Esta parte está marcada con color amarillo en el código de abajo:
//+------------------------------------------------------------------+ //| Establece el estado del gráfico | //+------------------------------------------------------------------+ void CWndEvents::SetChartState(void) { int awi=m_active_window_index; //--- Para determinar el estado cuando hay que desactivar la gestión bool condition=false; //--- Comprobamos las ventanas int windows_total=CWndContainer::WindowsTotal(); for(int i=0; i<windows_total; i++) { //--- Ir al siguiente si este formulario está ocultado if(!m_windows[i].IsVisible()) continue; //--- Comprobar condiciones en el manejador interno del formulario m_windows[i].OnEvent(m_id,m_lparam,m_dparam,m_sparam); //--- Si hay foco, lo anotamos if(m_windows[i].MouseFocus()) { condition=true; break; } } //--- Comprobamos listas desplegables if(!condition) { //--- Obtenemos el número total de listas desplegables int drop_lists_total=CWndContainer::DropListsTotal(awi); for(int i=0; i<drop_lists_total; i++) { //--- Obtenemos el puntero al combobox CListView *lv=m_wnd[awi].m_drop_lists[i]; //--- Si la lista está activada (abierta) if(lv.IsVisible()) { //--- Comprobamos el foco sobre la lista y el estado de su barra de desplazamiento if(m_wnd[awi].m_drop_lists[i].MouseFocus() || lv.ScrollState()) { condition=true; break; } } } } //--- Comprobamos el foco de los menús contextuales if(!condition) { //--- Obtenemos el número total de los menús contextuales desplegables int context_menus_total=CWndContainer::ContextMenusTotal(awi); for(int i=0; i<context_menus_total; i++) { //--- Si el foco está sobre el menú contextual if(m_wnd[awi].m_context_menus[i].MouseFocus()) { condition=true; break; } } } //--- Establecemos el estado del gráfico en todos los formularios for(int i=0; i<windows_total; i++) m_windows[i].CustomEventChartState(condition); }
Todo está listo para probar el control “Lista combinada”.
Prueba del control en la interfaz gráfica de la aplicación
Vamos a testear todo lo que hemos implementado en la quinta parte de la serie en la interfaz gráfica de la aplicación. En el artículo anterior, la prueba se ha terminado con tres listas. Vamos a dejarlas, y añadimos cuatro listas combinadas (Combobox) a la interfaz gráfica de la aplicación. Colocaremos dos listas combinadas de tal manera que se pueda probar una posible influencia de las listas desplegables sobre las listas estáticas, que van a encontrarse debajo de ellas. Además de eso, hay que probar cómo trabaja el método CWndEvents::SetChartState(). Por esa razón, colocaremos otros dos combobox de tal manera que, al abrir las listas desplegables, sus áreas salgan de los límites del formulario al que están adjuntadas.
En la clase personalizada (CProgram) de la aplicación de prueba, la clase de la lista combinada ya está disponible a través de las clases base. Creamos cuatro instancias de la clase tipo CComboBox y declaramos cuatro métodos para cada una de ella, indicando las márgenes desde el punto superior izquierdo del formulario (véase el código de abajo).
//+------------------------------------------------------------------+ //| Clase para crear la aplicación | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Combobox CComboBox m_combobox1; CComboBox m_combobox2; CComboBox m_combobox3; CComboBox m_combobox4; //--- private: //--- Combobox 1 #define COMBOBOX1_GAP_X (7) #define COMBOBOX1_GAP_Y (50) bool CreateComboBox1(const string text); //--- Combobox 2 #define COMBOBOX2_GAP_X (160) #define COMBOBOX2_GAP_Y (50) bool CreateComboBox2(const string text); //--- Combobox 3 #define COMBOBOX3_GAP_X (7) #define COMBOBOX3_GAP_Y (202) bool CreateComboBox3(const string text); //--- Combobox 4 #define COMBOBOX4_GAP_X (160) #define COMBOBOX4_GAP_Y (202) bool CreateComboBox4(const string text); };
Nosotros vamos a considerar sólo uno de estos métodos, porque se puede decir que son casi idénticos, a excepción de algunas propiedades ajustables. Por ejemplo, vamos a bloquear el cuarto combobox después de su creación. Abajo mostramos la secuencia de acciones principales para crear el control “Combobox”.
- Guardamos el puntero al formulario en la clase del control.
- Calculamos las coordenadas.
- Declaramos e inicializamos inmediatamente el array del texto para los elementos de la lista.
- Establecemos las propiedades del control. La mayoría de ellas están inicializadas con valores predefinidos. Pero si hace falta, se puede cambiarlos antes de la creación del control.
- Guardamos los valores de los elementos en la lista combobox.
- Si es necesario, establecemos las propiedades para la lista y la barra de desplazamiento.
- Seleccionar un elemento de la lista. Por defecto, queda seleccionado el primer (0) elemento.
- Creamos el control.
- Si es necesario, se puede bloquear el control. Como ejemplo, bloqueamos el cuarto combobox en nuestra aplicación de prueba.
- Al final de todo, hay que pasar el objeto a la clase base para guardar el puntero.
La secuencia de acciones puede ser diferente. Lo importante es mantener la secuencia de sólo tres acciones principales:
- Añadir el puntero del formulario a la clase del control. De lo contrario, la creación de la interfaz gráfica será interrumpida. La razón del fallo se puede encontrar en los mensajes del registro.
- Creación del control.
- Guardamos el puntero del control en la base de objetos.
//+------------------------------------------------------------------+ //| Crea el combobox 1 | //+------------------------------------------------------------------+ bool CProgram::CreateComboBox1(const string text) { //--- Número total de los elementos en la lista #define ITEMS_TOTAL1 8 //--- Pasar el objeto del formulario m_combobox1.WindowPointer(m_window1); //--- Coordenadas int x=m_window1.X()+COMBOBOX1_GAP_X; int y=m_window1.Y()+COMBOBOX1_GAP_Y; //--- Array de valores de elementos en la lista string items_text[ITEMS_TOTAL1]={"FALSE","item 1","item 2","item 3","item 4","item 5","item 6","item 7"}; //--- Establecemos las propiedades antes de la creación m_combobox1.XSize(140); m_combobox1.YSize(18); m_combobox1.LabelText(text); m_combobox1.ButtonXSize(70); m_combobox1.AreaColor(clrWhiteSmoke); m_combobox1.LabelColor(clrBlack); m_combobox1.LabelColorHover(clrCornflowerBlue); m_combobox1.ButtonBackColor(C'206,206,206'); m_combobox1.ButtonBackColorHover(C'193,218,255'); m_combobox1.ButtonBorderColor(C'150,170,180'); m_combobox1.ButtonBorderColorOff(C'178,195,207'); m_combobox1.ItemsTotal(ITEMS_TOTAL1); m_combobox1.VisibleItemsTotal(5); //--- Guardamos los valores de elementos en la lista de combobox for(int i=0; i<ITEMS_TOTAL1; i++) m_combobox1.ValueToList(i,items_text[i]); //--- Obtenemos el puntero de la lista CListView *lv=m_combobox1.GetListViewPointer(); //--- Establecemos las propiedades de la lista lv.LightsHover(true); lv.SelectedItemByIndex(lv.SelectedItemIndex()==WRONG_VALUE ? 2 : lv.SelectedItemIndex()); //--- Creamos el control if(!m_combobox1.CreateComboBox(m_chart_id,m_subwin,x,y)) return(false); //--- Añadimos el objeto al array común de los grupos de objetos CWndContainer::AddToElementsArray(0,m_combobox1); return(true); }
La llamada a los métodos de la creación de controles debe ubicarse en el método principal de la creación de la interfaz gráfica. En nuestro caso es CProgram::CreateTradePanel(). Abajo se muestra la versión reducida del método:
//+------------------------------------------------------------------+ //| Crea el panel de trading | //+------------------------------------------------------------------+ bool CProgram::CreateTradePanel(void) { //--- Creación del formulario 1 para los controles //--- Creación de controles: // Menú principal //--- Menús contextuales //--- Creación de la barra de estado //--- Comboboxs if(!CreateComboBox1("Combobox 1:")) return(false); if(!CreateComboBox2("Combobox 2:")) return(false); if(!CreateComboBox3("Combobox 3:")) return(false); if(!CreateComboBox4("Combobox 4:")) return(false); //--- Listas //--- Redibujar el gráfico m_chart.Redraw(); return(true); }
Añadimos el bloque para identificar los mensajes de los comboboxs con el identificador ON_CLICK_COMBOBOX_ITEM al manejador de eventos de la clase personalizada (CProgram). Hagamos que si llega el mensaje del tercer combobox, entonces dependiendo del elemento seleccionado en su lista, va a cambiarse el estado del cuarto combobox. En este caso, las selección de cualquier elemento a excepción del primero (0) en la lista va a hacer que el cuarto combobox esté disponible. La selección del primer elemento va a bloquearlo.
//+------------------------------------------------------------------+ //| Manejador de eventos | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Evento de la selección del elemento en el combobox if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM) { if(sparam==m_combobox1.LabelText()) ::Print(__FUNCTION__," > Es el mensaje del primer combobox > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); else if(sparam==m_combobox2.LabelText()) ::Print(__FUNCTION__," > Es el mensaje del segundo combobox > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); //--- Procesamos el mensaje del tercer combobox else if(sparam==m_combobox3.LabelText()) { ::Print(__FUNCTION__," > Es el mensaje del tercer combobox > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); //--- Si ha sido seleccionado el valor especificado, bloqueamos el cuarto combobox if(m_combobox3.ButtonText()=="FALSE") m_combobox4.ComboBoxState(false); //--- Si ha sido seleccionado otro valor, activamos el cuarto combobox else m_combobox4.ComboBoxState(true); } else if(sparam==m_combobox4.LabelText()) ::Print(__FUNCTION__," > Es el mensaje del cuarto combobox > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); } }
Si ahora compilamos el programa y los cargamos en el gráfico, obtendremos el siguiente resultado:
Fig. 2. Prueba del control “Combobox”.
Hemos terminado el desarrollo de la clase CComboBox para la creación de las listas combinadas.
Conclusión
En este artículo hemos analizado el control compuesto “Lista combinada” o “Combobox”. En esta fase del desarrollo de la librería para la creación de las interfaces gráficas, su esquema tiene el siguiente aspecto
Fig. 3. Estructura de la librería en la fase actual del desarrollo.
El siguiente artículo abrirá la sexta parte de la serie sobre el desarrollo de la librería para la creación de las interfaces gráficas. En los capítulos de la sexta parte vamos a crear las clases para la creación de los controles tales como: “Casillas de verificación”, “Campos de edición” y sus varios tipos combinados.
Más abajo puede descargar el material de la quinta parte de la serie para poder probar cómo funciona todo eso. Si le surgen algunas preguntas sobre el uso del material de estos archivos, puede dirigirse a la descripción detallada del proceso de desarrollo de la librería en uno de los artículos listados más abajo, o bien hacer su pregunta en los comentarios para el artículo.
Lista de artículos (capítulos) de la quinta parte: