Graphical Interfaces V: The Combobox Control (Chapter 3)
Contents
- Introduction
- The Combobox Control
- Writing a Class for Creating the Combobox Control
- Methods for Control Event Handling
- Connecting the Control Class with the Library Engine
- Testing the Combobox Control in the Graphical Interface of the Custom Application
- Conclusion
Introduction
The first article Graphical Interfaces I: Preparation of the Library Structure (Chapter 1) explains in detail what this library is for. You will find a list of articles with links at the end of each chapter. There, you can also download a complete version of the library at the current stage of development. The files must be placed in the same directories as they are located in the archive.
In the first two chapters of the fifth part of the series, we developed classes for creating a scrollbar and a view list. There we demonstrated how to attach a scrollbar to the element if the data does not fit the designated area. In this chapter, we will speak about creating a class for the combobox control. This is also a compound control containing, among others, elements considered in the previous chapters of the fifth part.
The Combobox Control
A combobox is a compound control, the main parts of which are (1) a button and (2) a list view. A list view in this case is a drop-down element and is called by pressing the button. After selecting a list view item, its text is displayed in the button and the list view is hidden. When a program contains many multi-option parameters, using a combobox will allow to create a compact graphical interface.
Below are the primitive objects for composing the combobox control.
- The element background
- Label (element description)
- Button
- Indication of a drop-down list view
Fig. 1. Compound parts of the combobox control
In the next part of the article, we are going to write a class for creating this control.
Writing a Class for Creating the Combobox Control
We are going to consider all stages of the development of the combobox control so that in the future this article could be used as an example for writing similar classes. At first, create an mqh file (ComboBox.mqh) and include in it all necessary files with the classes that will be required for creating a combobox. In our case, these will be three files with classes:
- CElement — base class for creating the control.
- CWindow — class of the form to which this control will be attached.
- CListView — class of the list view, visibility of which will be managed by the combobox.
//+------------------------------------------------------------------+ //| ComboBox.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" #include "ListView.mqh"
Then, create the CComboBox class and methods standard for each element of the library in the ComboBox.mqh file:
//+------------------------------------------------------------------+ //| Class for creating a combobox | //+------------------------------------------------------------------+ class CComboBox : public CElement { private: //--- Pointer to the form to which this element is attached CWindow *m_wnd; //--- public: CComboBox(void); ~CComboBox(void); //--- Stores the form pointer void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } //--- public: //--- Chart event handler virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- Timer virtual void OnEventTimer(void); //--- Moving the element virtual void Moving(const int x,const int y); //--- (1) Show, (2) hide, (3) reset, (4) delete virtual void Show(void); virtual void Hide(void); virtual void Reset(void); virtual void Delete(void); //--- (1) Set, (2) reset priorities of pressing the left mouse button virtual void SetZorders(void); virtual void ResetZorders(void); //--- Reset the color virtual void ResetColors(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CComboBox::CComboBox(void) { //--- Store the name of the element class in the base class CElement::ClassName(CLASS_NAME); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CComboBox::~CComboBox(void) { }
The user should have an opportunity to choose his own color scheme of the graphical interface. For that, he should have an access to the settings of the properties of the objects for composing the control. Below are the properties to set up before the control is created:
- Color of the control background
- Displayed description of the combobox (text label)
- Margins for the text label along the X and Y axes
- Colors of the text label in different states
- Text of the button (text of the selected list view item)
- Button size
- Colors of the button in different states
- Colors of the button frame in different states
- Colors of the button text in different states
- Icons for the arrow - the indication of a drop-down list views for the active and blocked modes
- Margins for the arrow icons along the X and Y axes
The code below contains the fields and methods for installing the properties listed above:
class CComboBox : public CElement { private: //--- Combobox properties: // Color of the general background color m_area_color; //--- Text and margins of the text label string m_label_text; int m_label_x_gap; int m_label_y_gap; //--- Colors of the text label in different states color m_label_color; color m_label_color_off; color m_label_color_hover; color m_label_color_array[]; //--- (1) Button text and (2) its size string m_button_text; int m_button_x_size; int m_button_y_size; //--- Colors of the button in different states color m_button_color; color m_button_color_off; color m_button_color_hover; color m_button_color_pressed; color m_button_color_array[]; //--- Colors of the button frame in different states color m_button_border_color; color m_button_border_color_off; //--- Color of the button text in different states color m_button_text_color; color m_button_text_color_off; //--- Label margins int m_drop_arrow_x_gap; int m_drop_arrow_y_gap; //--- Labels of the buttons with a drop-down menu in the active and blocked states string m_drop_arrow_file_on; string m_drop_arrow_file_off; //--- Priorities of the left mouse button click int m_area_zorder; int m_button_zorder; int m_zorder; //--- public: //--- (1) Background color, (2) sets and (3) returns the value of the text label 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); } //--- Margins of the text label 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) Returns the button text, (2) setting the button size 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) Background color, (2) colors of the text label 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; } //--- Button colors 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; } //--- Colors of the button frame void ButtonBorderColor(const color clr) { m_button_border_color=clr; } void ButtonBorderColorOff(const color clr) { m_button_border_color_off=clr; } //--- Colors of the button text void ButtonTextColor(const color clr) { m_button_text_color=clr; } void ButtonTextColorOff(const color clr) { m_button_text_color_off=clr; } //--- Setting icons for the button with a drop-down menu in the active and blocked states 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; } //--- Label margins 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; } };
The initialization of all the properties listed above by default values is in the class constructor:
//+------------------------------------------------------------------+ //| 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("") { //--- Set priorities of the left mouse button click m_zorder =0; m_area_zorder =1; m_button_zorder =2; }
A combobox will be created with five private methods, which will be called in the main public method CComboBox::CreateComboBox(). To gain access to the setting of the list view and the scrollbar properties, create methods for getting pointers to these elements:
class CComboBox : public CElement { private: //--- Objects for creating a combobox CRectLabel m_area; CLabel m_label; CEdit m_button; CBmpLabel m_drop_arrow; CListView m_listview; //--- public: //--- Methods for creating a 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: //--- Returns pointers to (1) the list view and (2) the scrollbar CListView *GetListViewPointer(void) { return(::GetPointer(m_listview)); } CScrollV *GetScrollVPointer(void) { return(m_listview.GetScrollVPointer()); } };
Out of the methods presented in the code above, we are going to consider only the CComboBox::CreateList() method for creating a list view in detail. Other methods do not contain anything that has not been studied in the previous articles of this series. Before that, however, we must introduce some changes to the CListView class of the list view.
A drop-down list view is always a compound part of another element. This means that its event handler may require tracking the focus over the element it is attached to. In our case it is a combobox. Add a field and a method to the CListView class for storing the pointer to the combobox to which the list view will be attached.
class CListView : public CElement { private: //--- Pointer to the element managing the list view visibility CElement *m_combobox; //--- public: //--- Stores the combobox pointer void ComboBoxPointer(CElement &object) { m_combobox=::GetPointer(object); } };
If the list view is a drop-down, add a check for the pointer to the main (public) method for creating a list view. If during the creation of the list view it turns out that there is no pointer, then the creation of the graphical interface will be terminated and a relevant message will be printed in the journal.
Below is a shortened version of the CListView::CreateListView() method:
//+------------------------------------------------------------------+ //| Creates a list view | //+------------------------------------------------------------------+ bool CListView::CreateListView(const long chart_id,const int window,const int x,const int y) { //--- Leave, if there is no form pointer //--- If the list view is a drop-down, a pointer to the combobox to which it will be attached is required if(CElement::IsDropdown()) { //--- Leave, if there is no pointer to the combobox if(::CheckPointer(m_combobox)==POINTER_INVALID) { ::Print(__FUNCTION__," > Before creating a drop-down list view, the class must be passed " "a pointer to the combobox: CListView::ComboBoxPointer(CElement &object)"); return(false); } } //--- Initializing variables //--- Margins from the edge //--- Creating a button //--- Hide the element if the window is a dialog one or is minimized //--- return(true); }
Now, we are going to return to the development of the combobox class (CComboBox). The property indicating that the list view will be a drop-down will have to be set at the very early stage, that is in the class constructor:
CComboBox::CComboBox(void) { //--- Drop-down list view mode m_listview.IsDropdown(true); }
When creating a list view, the pointers to the form and the combobox to which the list view will be attached must be stored at the beginning of the method. Please note that the list view and the combobox must have common identifier as in this case they make up one control. After the list view was created, it must be hidden.
//+------------------------------------------------------------------+ //| Creates a list view | //+------------------------------------------------------------------+ bool CComboBox::CreateList(void) { //--- Store pointers to the form and the combobox m_listview.WindowPointer(m_wnd); m_listview.ComboBoxPointer(this); //--- Coordinates int x=CElement::X2()-m_button_x_size; int y=CElement::Y()+m_button_y_size; //--- Set properties m_listview.Id(CElement::Id()); m_listview.XSize(m_button_x_size); //--- Create the control if(!m_listview.CreateListView(m_chart_id,m_subwin,x,y)) return(false); //--- Hide the list view m_listview.Hide(); return(true); }
To set the number of list view items and fill the list view with values, add the relevant methods to the CComboBox class:
class CListView : public CElement { public: //--- Setting (1) the size of the list view (number of items) and (2) its visible part void ItemsTotal(const int items_total) { m_listview.ListSize(items_total); } void VisibleItemsTotal(const int visible_items_total) { m_listview.VisibleListSize(visible_items_total); } //--- Stores the passed value in the list view by specified index void ValueToList(const int item_index,const string item_text); }; //+------------------------------------------------------------------+ //| Stores passed value in the list view by specified index | //+------------------------------------------------------------------+ void CComboBox::ValueToList(const int item_index,const string item_text) { m_listview.ValueToList(item_index,item_text); }
To select (highlight) a list view item, create the CComboBox::SelectedItemByIndex() method. The index of the item that has to be highlighted is the only argument that has to be passed to this method. Then, highlighting of the item is carried out in the eponymous method of the list view (CListView), where the index is adjusted if the range is exceeded. After that, the item text is stored and set in the combobox button.
class CListView : public CElement { public: //--- Highlighting the item by specified index void SelectedItemByIndex(const int index); }; //+------------------------------------------------------------------+ //| Highlighting the item by specified index | //+------------------------------------------------------------------+ void CComboBox::SelectedItemByIndex(const int index) { //--- Highlight the item in the list view m_listview.SelectedItemByIndex(index); //--- Store and set the text in the button m_button_text=m_listview.SelectedItemText(); m_button.Description(m_listview.SelectedItemText()); }
We will also need a method for blocking and unblocking the element and also one for getting its current state. Depending on the new state of the element, colors corresponding to this state are set for its objects. Examples will be shown further in the article.
class CListView : public CElement { public: //--- Getting and setting the element state bool ComboBoxState(void) const { return(m_combobox_state); } void ComboBoxState(const bool state); }; //+------------------------------------------------------------------+ //| Changing the combobox state | //+------------------------------------------------------------------+ void CComboBox::ComboBoxState(const bool state) { m_combobox_state=state; //--- Set colors corresponding to the current state to the object 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); }
When the mouse cursor is hovering over the element objects, changing their color using the CComboBox::ChangeObjectsColor() method will take place only when that element is available:
class CListView : public CElement { public: //--- Changing the object color when the cursor is hovering over it void ChangeObjectsColor(void); }; //+------------------------------------------------------------------+ //| Changing the object color when the cursor is hovering over it | //+------------------------------------------------------------------+ void CComboBox::ChangeObjectsColor(void) { //--- Leave, if the element is blocked if(!m_combobox_state) return; //--- Change the object color 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); }
The CComboBox::ChangeObjectsColor() method must be called in the timer of the control CComboBox::OnEventTimer(). Skipping ahead, it should be mentioned that a combobox can be a compound part of a more complex element that is also a drop down. In one of the future articles, we will speak about a drop-down calendar - an example of such an element. A compound part of it is a combobox. Conditions for changing the color in the timer for such an element will be formed as shown in the code below:
- If this is a drop-down element and the list view is hidden.
- If the first condition is not met, check the availability of the form and the element itself.
//+------------------------------------------------------------------+ //| Timer | //+------------------------------------------------------------------+ void CComboBox::OnEventTimer(void) { //--- If this is a drop-down element and the list view is hidden if(CElement::IsDropdown() && !m_listview.IsVisible()) ChangeObjectsColor(); else { //--- If the form and the element are not blocked if(!m_wnd.IsLocked() && m_combobox_state) ChangeObjectsColor(); } }
Let us create the CComboBox::ChangeComboboxListState() method for managing the visibility of the list view of the combobox. This method will change the current state of the combobox for the opposite one. At the beginning of the method, there will be a check for the the element availability. If the combobox is blocked, the program will leave the method. Then the code will be split into two branches:
- If the list view is already visible, it will be hidden here and corresponding colors will be set for the button of the combobox. After that, if this is not a drop-down element, the form must be unblocked and the identifier of the active element must be reset.
- If the list is hidden, it has to be made visible and the colors corresponding to this state must be set for the button of the combobox. At the end of the branch, the form must be blocked and the identifier of the activating element must be stored.
class CListView : public CElement { public: //--- Changes the current state of the combobox for the opposite void ChangeComboBoxListState(void); }; //+------------------------------------------------------------------+ //| Changes the current state of the combobox for the opposite | //+------------------------------------------------------------------+ void CComboBox::ChangeComboBoxListState(void) { //--- Leave, if the element is blocked if(!m_combobox_state) return; //--- If the list view is visible if(m_listview.IsVisible()) { //--- Hide the list view m_listview.Hide(); //--- Set colors m_label.Color(m_label_color_hover); m_button.BackColor(m_button_color_hover); //--- If this is not a drop-down element if(!CElement::IsDropdown()) { //--- Unblock the form m_wnd.IsLocked(false); m_wnd.IdActivatedElement(WRONG_VALUE); } } //--- If the list view is hidden else { //--- Show the list view m_listview.Show(); //--- Set colors m_label.Color(m_label_color_hover); m_button.BackColor(m_button_color_pressed); //--- Block the form m_wnd.IsLocked(true); m_wnd.IdActivatedElement(CElement::Id()); } }
Methods for Control Event Handling
We can move on to setting the CComboBox::OnEvent() control event handler. For that, we will need two auxiliary private methods:
- CComboBox::OnClickButton() – the combobox button press event will be tracked in this method.
- CComboBox::CheckPressedOverButton() – the state of the left mouse button over the combobox button will be tracked here.
class CListView : public CElement { private: //--- Handling of pressing the button bool OnClickButton(const string clicked_object); //--- Checking the pressed left mouse button over the combobox button void CheckPressedOverButton(void); };
Code of the CComboBox::OnClickButton() method is very simple. It will contain only a check of the object name on which the pressing took place and a call of the CComboBox::ChangeComboBoxListState() method discussed previously, which will manage the visibility of the combobox list view as shown in the code below.
//+------------------------------------------------------------------+ //| Pressing on the combobox button | //+------------------------------------------------------------------+ bool CComboBox::OnClickButton(const string clicked_object) { //--- Leave, if the object name is different if(clicked_object!=m_button.Name()) return(false); //--- Change the list view state ChangeComboboxListState(); return(true); }
Code of the CComboBox::CheckPressedOverButton() method is presented below. A check for the form availability and the identifier of the activating element is carried out at the beginning of the method. The program will leave the method if the form is blocked and identifiers do not match.
Then, if there is no focus over the element, we check the presence of the focus over the list view and the state of the scrollbar. If the focus turns out to be not over the list view or the scrollbar is in the mode of the slider movement, then the program will leave the method. As you know, the slider of the scrollbar if it is in the movement mode, can be moved even if the cursor leaves the slider area. If none of these conditions is met, then:
(1) the list view is hidden,
(2) colors of the element objects are restored,
and at the end of this block of code, if identifiers do not match and the element is not a drop-down, (3) the form must be unblocked. Let me remind you that a form can be unblocked only by the element that blocked it.
In case when there is a focus over the element, at first a check for the visibility of the list view is carried out. If the list is visible, then there is no point in continuing and the program leaves the method. If the list is hidden, then corresponding colors are set depending on the focus over the combobox button.
//+------------------------------------------------------------------+ //| Check of the pressed left mouse button over the button | //+------------------------------------------------------------------+ void CComboBox::CheckPressedOverButton(void) { //--- Leave, if the form is blocked and identifiers do not match if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id()) return; //--- If there is no focus if(!CElement::MouseFocus()) { //--- Leave, if the focus is not over the list view or the scrollbar is enabled if(m_listview.MouseFocus() || m_listview.ScrollState()) return; //--- Hide the list view m_listview.Hide(); //--- Restore colors ResetColors(); //--- If identifiers match and the element is not a drop-down if(m_wnd.IdActivatedElement()==CElement::Id() && !CElement::IsDropdown()) //--- Unblock the form m_wnd.IsLocked(false); } //--- If there is focus else { //--- Leave, if the list view is visible if(m_listview.IsVisible()) return; //--- Set the color considering the focus if(m_button.MouseFocus()) m_button.BackColor(m_button_color_pressed); else m_button.BackColor(m_button_color_hover); } }
Call of the CComboBox::OnClickButton() method must be passed in the block of handling the graphical object press event which can be identified by the CHARTEVENT_OBJECT_CLICK identifier:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CComboBox::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Handling the left mouse button press on the object even if(id==CHARTEVENT_OBJECT_CLICK) { //--- Pressing on the combobox button if(OnClickButton(sparam)) return; } }
Before calling the CComboBox::CheckPressedOverButton() method, the following checks must be passed in the block of handling the mouse cursor move event CHARTEVENT_MOUSE_MOVE:
- the element visibility;
- the element availability;
- left mouse button state.
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CComboBox::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Handling of the cursor movement event if(id==CHARTEVENT_MOUSE_MOVE) { //--- Leave, if the element is hidden if(!CElement::IsVisible()) return; //--- Coordinates int x=(int)lparam; int y=(int)dparam; //--- Checking the focus over the elements 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()); //--- Leave, if the element is blocked if(!m_combobox_state) return; //--- Leave, if the left mouse button is released if(sparam=="0") return; //--- Check of the left mouse button pressed over a split button CheckPressedOverButton(); return; } }
At the moment of pressing on one of the list view items, the ON_CLICK_LIST_ITEM custom event is generated as shown in the code below. This message must be received in the combobox event handler. If the element identifiers match, that is the message came from the list view attached to this combobox, then store the text of the highlighted item of the list view and hide the list view using the CComboBox::ChangeComboBoxListState() method.
To ensure the connection with the application under development, this message with the ON_CLICK_LIST_ITEM identifier can be received in the CProgram custom class. A message can be also sent from the combobox with its unique (1) event identifier, (2) element identifier (3) and combobox description. To broaden the capacity of the control event identification, we are going to cater for such an option too. Add a unique identifier for the combobox control to the Defines.mqh file.
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #define ON_CLICK_COMBOBOX_ITEM (17) // Selecting an item in the combobox list view
In this case, add a line highlighted in the code below in blue to the control event handler:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CComboBox::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Handling the list view item press event if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM) { //--- If identifiers match if(lparam==CElement::Id()) { //--- Store and set the text in the button m_button_text=m_listview.SelectedItemText(); m_button.Description(m_listview.SelectedItemText()); //--- Change the list view state ChangeComboBoxListState(); //--- Send a message about it ::EventChartCustom(m_chart_id,ON_CLICK_COMBOBOX_ITEM,CElement::Id(),0,m_label_text); } //--- return; } }
We can also set hiding the list view when the chart properties are changed. For that, an event with the CHARTEVENT_CHART_CHANGE identifier must be handled as shown in the code below:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CComboBox::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Handling the chart properties change event if(id==CHARTEVENT_CHART_CHANGE) { //--- Leave, if the element is blocked if(!m_combobox_state) return; //--- Hide the list view m_listview.Hide(); //--- Restore colors ResetColors(); //--- Unblock the form m_wnd.IsLocked(false); m_wnd.IdActivatedElement(WRONG_VALUE); return; } }
The class for creating the combobox control is ready for testing but before that it must be connected with the library engine for correct work.
Connecting the Control Class with the Library Engine
Connecting an control with the library engine takes only a few simple actions:
1.Include the file with the control class in the WndContainer.mqh root file of the library.
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "ComboBox.mqh"
2.If necessary, create a private array for the element and a method for creating the size of this array. In our case, a private array will be required for drop-down list views.
class CWndContainer { protected: //--- Structure of element arrays struct WindowElements { //--- Common array of all objects //--- Common array of all elements //--- Private arrays of elements: // Array of context menus //--- Array of main menus //--- Tooltips //--- Array of drop-down list views of different types CElement *m_drop_lists[]; }; //--- Array of element arrays for each window WindowElements m_wnd[]; //--- public: //--- Number of drop-down list views int DropListsTotal(const int window_index); }; //+------------------------------------------------------------------+ //| Returns number of drop-down list views by specified window index | //+------------------------------------------------------------------+ 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.Create a private method for adding pointers to the private array. In this case, we need to get a list view pointer from the combobox control and add it to the private array. Besides, list view object pointers and the list view scrollbars must be added to the common array of the objects. Code of the CWndContainer::AddComboBoxElements() method is presented below.
class CWndContainer { private: //--- Stores pointers to the context menu elements in the base //--- Stores pointers to the main menu elements in the base //--- Stores pointers to the split button elements in the base //--- Stores pointers to the tooltip elements in the base //--- Stores pointers to the list view objects in the base //--- Stores pointers to the drop-down list views elements in the base bool AddComboBoxElements(const int window_index,CElement &object); }; //+------------------------------------------------------------------+ //| Stores the pointer to the drop-down list view in a private array | //+------------------------------------------------------------------+ bool CWndContainer::AddComboBoxElements(const int window_index,CElement &object) { //--- Leave, if this is not a tooltip if(object.ClassName()!="CComboBox") return(false); //--- Get the combobox pointer CComboBox *cb=::GetPointer(object); //--- for(int i=0; i<2; i++) { //--- Increasing the element array int size=::ArraySize(m_wnd[window_index].m_elements); ::ArrayResize(m_wnd[window_index].m_elements,size+1); //--- Add the list to the base if(i==0) { CListView *lv=cb.GetListViewPointer(); m_wnd[window_index].m_elements[size]=lv; AddToObjectsArray(window_index,lv); //--- Add the pointer to the private array AddToRefArray(lv,m_wnd[window_index].m_drop_lists); } //--- Add the scrollbar to the base else if(i==1) { CScrollV *sv=cb.GetScrollVPointer(); m_wnd[window_index].m_elements[size]=sv; AddToObjectsArray(window_index,sv); } } //--- return(true); }
4.If the actions of item 3 were required, do not forget to call the CWndContainer::AddComboBoxElements() method in the main method CWndContainer::AddToElementsArray() where calls of such methods must be located.
//+------------------------------------------------------------------+ //| Adds a pointer to the element array | //+------------------------------------------------------------------+ void CWndContainer::AddToElementsArray(const int window_index,CElement &object) { //--- If the base does not contain forms for controls //--- If the request is for a non-existent form //--- Add to the common array of elements //--- Add element objects to the common array of objects //--- Store the id of the last element in all forms //--- Increase the counter of the element identifiers //--- Stores pointers to the context menu objects in the base //--- Stores pointers to the main menu objects in the base //--- Stores pointers to the split button objects in the base //--- Stores pointers to the tooltip objects in the base //--- Stores pointers to the list view objects in the base //--- Stores pointers to the combobox control objects in the base if(AddComboBoxElements(window_index,object)) return; }
When an element is a drop-down, it sometimes may exceed the form boundaries depending on the location of this element on the form. The location of the mouse cursor must be controlled at all times and the chart scrolling must be disabled if the chart is over one of such elements. This will allow to avoid scrolling the chart when the left mouse button is pressed over the drop-down element. For that, we have already written the CWndEvents::SetChartState() method in the CWndEvents class. Now, it has to be enriched with the check of drop-down list views. In the code below, this part is highlighted in yellow:
//+------------------------------------------------------------------+ //| Sets the chart state | //+------------------------------------------------------------------+ void CWndEvents::SetChartState(void) { int awi=m_active_window_index; //--- To identify the event when management must be disabled bool condition=false; //--- Check windows int windows_total=CWndContainer::WindowsTotal(); for(int i=0; i<windows_total; i++) { //--- Move to the next one, if this form is hidden if(!m_windows[i].IsVisible()) continue; //--- Check conditions in the internal handler of the form m_windows[i].OnEvent(m_id,m_lparam,m_dparam,m_sparam); //--- If there is a focus, mark it if(m_windows[i].MouseFocus()) { condition=true; break; } } //--- Check drop-down list views if(!condition) { //--- Get the total of the drop-down list views int drop_lists_total=CWndContainer::DropListsTotal(awi); for(int i=0; i<drop_lists_total; i++) { //--- Get the pointer to the drop-down list view CListView *lv=m_wnd[awi].m_drop_lists[i]; //--- If the list view is activated (visible) if(lv.IsVisible()) { //--- Check the focus over the list view and the state of its scrollbar if(m_wnd[awi].m_drop_lists[i].MouseFocus() || lv.ScrollState()) { condition=true; break; } } } } //--- Check the focus of context menus if(!condition) { //--- Check the total of drop-down context menus int context_menus_total=CWndContainer::ContextMenusTotal(awi); for(int i=0; i<context_menus_total; i++) { //--- If the focus is over the context menu if(m_wnd[awi].m_context_menus[i].MouseFocus()) { condition=true; break; } } } //--- Sets the chart state in all forms for(int i=0; i<windows_total; i++) m_windows[i].CustomEventChartState(condition); }
We have everything ready for testing the combobox control.
Testing the Combobox Control in the Graphical Interface of the Custom Application
Let us test everything that was implemented in the graphical interface of the custom application in the fifth part of the series. The test in the previous article was finished with three list views. Let us keep the list views and add four comboboxes to the graphical interface of the application. Place two comboboxes in the way that will allow us to test the impact of the drop-down list views on the static list views that will be located underneath them. In addition, we have to test the work of the CWndEvents::SetChartState() method. For that, we will place comboboxes so that when drop-down list views are visible, they exceed the boundaries of the form to which they are attached.
In the custom class CProgram of the test application, the class of the combobox is already available through the base classes. Create four instances of the class of the CComboBox type and declare four methods for each of them with specifying margins from the top left point of the form as shown in the code below.
//+------------------------------------------------------------------+ //| Class for creating the application | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Comboboxes 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); };
Let us consider one of these methods as all of them are identical with the exception of the properties set by the user. For instance, we will block the fourth combobox straight after its creation. Below is the sequence of actions for creating the combobox control.
- Store the form pointer in the control class.
- Calculate coordinates.
- Declare and initialize straight away the text array for the list view items.
- Set the properties of control. Majority of them are initialized by default values. The values can be redefined before creating the control if the necessity arises.
- Store the item values in the combobox list view.
- If required, set the properties for the list view and the scrollbar.
- Highlight the item in the list view. The first item (0) will be highlighted by default.
- Create the control.
- Control can be blocked if necessary. We will block the fourth combobox in our test application as an example.
- At the end of the method pass the object to the base class for storing the pointer.
Sequence of actions can be different. The important thing is to keep the sequence of three main actions:
- Adding the form pointer to the control class. Otherwise the creation of the graphical interface will be terminated. The reason of the failure can be found out from the messages in the journal.
- Creating the control.
- Storing the control pointer in the object base.
//+------------------------------------------------------------------+ //| Creates combobox 1 | //+------------------------------------------------------------------+ bool CProgram::CreateComboBox1(const string text) { //--- Total number of the list view items #define ITEMS_TOTAL1 8 //--- Pass the form object m_combobox1.WindowPointer(m_window1); //--- Coordinates int x=m_window1.X()+COMBOBOX1_GAP_X; int y=m_window1.Y()+COMBOBOX1_GAP_Y; //--- Array of the item values in the list view string items_text[ITEMS_TOTAL1]={"FALSE","item 1","item 2","item 3","item 4","item 5","item 6","item 7"}; //--- Set properties before creation 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); //--- Store the item values in the combobox list view for(int i=0; i<ITEMS_TOTAL1; i++) m_combobox1.ValueToList(i,items_text[i]); //--- Get the list view pointer CListView *lv=m_combobox1.GetListViewPointer(); //--- Set the list view properties lv.LightsHover(true); lv.SelectedItemByIndex(lv.SelectedItemIndex()==WRONG_VALUE ? 2 : lv.SelectedItemIndex()); //--- Create the control if(!m_combobox1.CreateComboBox(m_chart_id,m_subwin,x,y)) return(false); //--- Add the object to the common array of the object groups CWndContainer::AddToElementsArray(0,m_combobox1); return(true); }
Call of the methods for creating controls must be placed in the main method for creating the graphical interface. In our case this is CProgram::CreateTradePanel(). Below is a shortened version of the method:
//+------------------------------------------------------------------+ //| Creates the trading panel | //+------------------------------------------------------------------+ bool CProgram::CreateTradePanel(void) { //--- Creating form 1 for controls //--- Creating controls: // Main menu //--- Context menus //--- Creating the status bar //--- Comboboxes if(!CreateComboBox1("Combobox 1:")) return(false); if(!CreateComboBox2("Combobox 2:")) return(false); if(!CreateComboBox3("Combobox 3:")) return(false); if(!CreateComboBox4("Combobox 4:")) return(false); //--- List views //--- Redrawing of the chart m_chart.Redraw(); return(true); }
Add the block for identifying messages from comboboxes with the ON_CLICK_COMBOBOX_ITEM identifier to the event handler of the CProgram custom class. Let us ensure that if a message from the third combbobox is received, then depending on what item of the list view was selected, the state of the fourth combobox will change. In our case, selecting any item in the list view except the first one (0) will make the fourth combobox available. Selecting the first item will block it.
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Selection of item in combobox event if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM) { if(sparam==m_combobox1.LabelText()) ::Print(__FUNCTION__," > This message is from combobox 1 > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); else if(sparam==m_combobox2.LabelText()) ::Print(__FUNCTION__," > This message is from combobox 2 > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); //--- Handle the message from the third combobox else if(sparam==m_combobox3.LabelText()) { ::Print(__FUNCTION__," > This message is from combobox 3 > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); //--- If the specified value has been selected, disable combobox 4 if(m_combobox3.ButtonText()=="FALSE") m_combobox4.ComboBoxState(false); //--- If another value has been selected, enable combobox 4 else m_combobox4.ComboBoxState(true); } else if(sparam==m_combobox4.LabelText()) ::Print(__FUNCTION__," > This message is from combobox 4 > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); } }
If we compile the program and load it on to the chart, the result will be as in the screenshot below:
Fig. 2. Testing the combobox control.
We have completed the development of the CComboBox class for creating a combobox.
Conclusion
In this article we have considered a compound control combobox. Schematic of the library for creating graphical interfaces at the current stage of development looks as shown below:
Fig. 3. Structure of the library at the current stage of development.
The next article will be the beginning of the sixth part of the series dedicated to the development of the library for creating graphical interfaces. There, we will write classes for creating the checkbox and edit controls and their mixed types.
You can download the material of Part V and test how it works. If you have questions on using material from those files, you can refer to the detailed description of the library development in one of the articles from the list below or ask your question in the comments of this article.
List of articles of the fifth part:
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/2381
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
Any reason why the comments are not in English anymore?
I have ssen the same, and think they have currently 2 things.
1. Some issues still to be needed to be fixed (especially in MT4 !)
2. Currently no Person who can translate it to english. For me it is also not clear, why the main comments are not in english. But so long the functions declared in english, and the most examples like above is available in english, it doesn´t matters.
New article Graphical Interfaces V: The Combobox Control (Chapter 3) has been published:
Author: Anatoli Kazharski
Good job with the articles. I am enjoying the next series of articles. Can you please check SplitButton.mqh line 90? I am getting error
return - cannot convert from const pointer to nonconst pointer.
return - cannot convert from const pointer to nonconst pointer.
Yes. It is necessary to remove the const wherever you get this error. Later I will update files in all articles.
[WIN7 64bits - MetaTrader5 build 1472]
Strange behavior of SplitString with ComboBox.
I create a combobox to control the sl e tp for a order, but when I tried get the selected item there is problem.
When the user click in the sell or buy buttons, the program get the selected item from combobox and calculate the sl and tp for the order.
When I run it in Debugging mode (F5) there no problem. But when I drag it from the EA list and drop to the chart, the problem appears.
Seens like the EasyAndFast library override in some way the SplitString, or PrintFormat, or StringToDouble functions, or something like that. But I not sure at all.
Here is my example:
//| Creates combobox TP:SL |
//+------------------------------------------------------------------+
bool CProgram::CreateTPSLComboBox(const int x_gap, const int y_gap, const string text) {
// Total number of the list view items
#define ITEMS_TOTAL1 4
// Pass the panel object
_tpslCombobox.WindowPointer(_mainWindow);
// Attach to the first tab
_mainTabs.AddToElementsArray(0, _tpslCombobox);
// Coordinates
int x = _mainWindow.X() + x_gap;
int y = _mainWindow.Y() + y_gap;
// Array of the item values in the list view
string items_text[ITEMS_TOTAL1] = {"2:4", "2:3", "3:4", "0:0"};
// Set properties before creation
_tpslCombobox.XSize(90);
_tpslCombobox.YSize(18);
_tpslCombobox.LabelText(text);
_tpslCombobox.ButtonXSize(45);
_tpslCombobox.ItemsTotal(ITEMS_TOTAL1);
_tpslCombobox.VisibleItemsTotal(4);
_tpslCombobox.AreaColor(clrWhite);
// Store the item values in the combobox list view
for(int i = 0; i < ITEMS_TOTAL1; i++)
_tpslCombobox.ValueToList(i, items_text[i]);
// Get the list view pointer
CListView *lv = _tpslCombobox.GetListViewPointer();
// Set the list view properties
lv.LightsHover(true);
lv.SelectedItemByIndex(lv.SelectedItemIndex() == WRONG_VALUE ? 0 : lv.SelectedItemIndex());
// Create control
if(!_tpslCombobox.CreateComboBox(m_chart_id, m_subwin, x, y))
return(false);
// Add the object to the common array of object groups
CWndContainer::AddToElementsArray(0, _tpslCombobox);
return(true);
}
...
//+------------------------------------------------------------------+
//| Sell function |
//+------------------------------------------------------------------+
void CProgram::Sell() {
string sep = ":";
ushort usep;
usep = StringGetCharacter(sep, 0);
string s[];
double tp = 0.0;
double sl = 0.0;
if(StringSplit(_tpslCombobox.ButtonText(), usep, s) == 2) {
PrintFormat("s01: %s - s02: %s", s[0], s[1]); // output: s01: 2 - s02: 4
tp = StringToDouble(s[0]);
sl = StringToDouble(s[1]);
}
PrintFormat("tp: %.2f - sl: %.2f", tp, sl); // output: tp: 2.00 - sl: 2.00
...
The output: "tp:2.00 - sl: 2.00" should be "tp:2.00 - sl: 4.00".
------But again, the problem appears only when I drag and drop to the chart. When I run it in debug mode everything is ok.
Some advise?
UPDATE:
It looks like a problem in MetaTrader 5, not in the EasyAndFast library specifically.
Here is a test:
//| test.mq5 |
//| Copyright 2016, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
//---
string sep = ":";
ushort usep;
usep = StringGetCharacter(sep, 0);
string s[];
double tp = 0.0;
double sl = 0.0;
if(StringSplit("2:4", usep, s) == 2) {
tp = StringToDouble(s[0]);
sl = StringToDouble(s[1]);
}
PrintFormat("tp: %.2f - sl: %.2f", tp, sl);
}
//+------------------------------------------------------------------+
Output: "tp: 2.00 - sl: 2.00" when you drag and drop the script to the chart.
If someone else confirm this, I will report to ServiceDesk.
Thank you.
[WIN7 64bits - MetaTrader5 build 1472]