Graphical Interfaces III: Groups of Simple and Multi-Functional Buttons (Chapter 2)
Contents
- Introduction
- Developing the Class for Creating Groups of Simple Buttons
- Developing the Class for Creating Groups of Radio Buttons
- Developing the Class for Creating Groups of Icon Buttons
- Conclusion
Introduction
The first article Graphical Interfaces I: Preparation of the Library Structure (Chapter 1) considers in detail what this library is for. A full list of the links to the articles is at the end of each chapter of the series. There, you can also find and download a complete version of the library at the current stage of development. The files must be located in the same directories as in the archive.
The first article of the third part of the series was dedicated to simple and multi-functional buttons. In the second article, we will discuss groups of interconnected buttons. They are used for creating elements in applications allowing a user to select one of the options from a set (group).
Developing the Class for Creating Groups of Simple Buttons
A group of simple buttons is essentially an array of graphical objects of the OBJ_BUTTON type. The differential characteristic of such controls is that only one button of the group can be pressed at a time. At this stage, a class of this control can be created in two ways:
- by creating a group from already implemented control of the CSimpleButton type;
- by creating a group from primitive objects of the CButton type.
The second option is simpler as it does not require the creation of an additional method for each control of the CSimpleButton type to get to the base of pointers. We are going to use this one.
Create the ButtonsGroup.mqh file with the CButtonsGroup class in the Controls folder containing all controls and include it in the WndContainer.mqh file. The class must have virtual methods and the form pointer as it has been shown for all other previously developed controls. We are not going to discuss them here and go straight to the description of properties and methods for their construction.
Some properties will be common for each button inside one group but there will be unique ones too. Below are two groups of properties related to the appearance of buttons.
Common properties of buttons:
- heights;
- color of blocked buttons;
- frame color in the available and blocked modes;
- text color in different states;
- priority for the left mouse click.
Unique properties of buttons:
- button state (pressed/unpressed);
- margins from the edge point of the form;
- text;
- width;
- colors of buttons in different states;
- gradients for buttons.
Margins for each button will allow to arrange buttons in any sequence.
Fig. 1. Examples of arranging buttons in a group.
Declare dynamic arrays for the objects of the CButton type and unique properties of buttons as shown in the code below.
//+------------------------------------------------------------------+ //| Class for creating a group of simple buttons | //+------------------------------------------------------------------+ class CButtonsGroup : public CElement { private: //--- Objects for creating a button CButton m_buttons[]; //--- Button gradients struct ButtonsGradients { color m_buttons_color_array[]; }; ButtonsGradients m_buttons_total[]; //--- Button properties: // Arrays for unique properties of buttons bool m_buttons_state[]; int m_buttons_x_gap[]; int m_buttons_y_gap[]; string m_buttons_text[]; int m_buttons_width[]; color m_buttons_color[]; color m_buttons_color_hover[]; color m_buttons_color_pressed[]; //--- Height of buttons int m_button_y_size; //--- Color of blocked buttons color m_back_color_off; //--- Color frame in the active and blocked modes color m_border_color; color m_border_color_off; //--- Text color color m_text_color; color m_text_color_off; color m_text_color_pressed; //--- Priority of left mouse click int m_buttons_zorder; //--- public: //--- Number of buttons int ButtonsTotal(void) const { return(::ArraySize(m_buttons)); } //--- (1) Height of buttons void ButtonYSize(const int y_size) { m_button_y_size=y_size; } //--- (1) Background colors of a blocked button and a frame ((2) available/(3) blocked) void BackColorOff(const color clr) { m_back_color_off=clr; } void BorderColor(const color clr) { m_border_color=clr; } void BorderColorOff(const color clr) { m_border_color_off=clr; } //--- Text color void TextColor(const color clr) { m_text_color=clr; } void TextColorOff(const color clr) { m_text_color_off=clr; } void TextColorPressed(const color clr) { m_text_color_pressed=clr; } };
The size of dynamic arrays will be defined at the time of forming a group of buttons before its creation (attaching to the chart). Every time the CButtonsGroup::AddButton() method is called, the arrays will be increased by one element which will be filled with passed parameters.
class CButtonsGroup : public CElement { public: //--- Adds a button with specified properties before creation void AddButton(const int x_gap,const int y_gap,const string text,const int width, const color button_color,const color button_color_hover,const color button_color_pressed); }; //+------------------------------------------------------------------+ //| Adds a button | //+------------------------------------------------------------------+ void CButtonsGroup::AddButton(const int x_gap,const int y_gap,const string text,const int width, const color button_color,const color button_color_hover,const color pressed_button_color) { //--- Increase the array size by one element int array_size=::ArraySize(m_buttons); ::ArrayResize(m_buttons,array_size+1); ::ArrayResize(m_buttons_total,array_size+1); ::ArrayResize(m_buttons_state,array_size+1); ::ArrayResize(m_buttons_x_gap,array_size+1); ::ArrayResize(m_buttons_y_gap,array_size+1); ::ArrayResize(m_buttons_text,array_size+1); ::ArrayResize(m_buttons_width,array_size+1); ::ArrayResize(m_buttons_color,array_size+1); ::ArrayResize(m_buttons_color_hover,array_size+1); ::ArrayResize(m_buttons_color_pressed,array_size+1); //--- Store the values of passed parameters m_buttons_x_gap[array_size] =x_gap; m_buttons_y_gap[array_size] =y_gap; m_buttons_text[array_size] =text; m_buttons_width[array_size] =width; m_buttons_color[array_size] =button_color; m_buttons_color_hover[array_size] =button_color_hover; m_buttons_color_pressed[array_size] =pressed_button_color; m_buttons_state[array_size] =false; }
When creating a group of buttons, the process of creating the graphical interface will be stopped and a relevant message will be printed in the journal if before that no buttons were added using the CButtonsGroup::AddButton() method. Buttons will be created in a loop as shown in the shortened version of the CButtonsGroup::CreateButtons() method in the code below.
class CButtonsGroup : public CElement { public: //--- Methods for creating a button bool CreateButtonsGroup(const long chart_id,const int subwin,const int x,const int y); //--- private: bool CreateButtons(void); }; //+------------------------------------------------------------------+ //| Creates buttons | //+------------------------------------------------------------------+ bool CButtonsGroup::CreateButtons(void) { //--- Coordinates int l_x =m_x; int l_y =m_y; //--- Get the number of buttons int buttons_total=ButtonsTotal(); //--- If there is no button in a group, report if(buttons_total<1) { ::Print(__FUNCTION__," > This method is to be called " "if a group contains at least one button! Use the CButtonsGroup::AddButton() method"); return(false); } //--- Create the specified number of buttons for(int i=0; i<buttons_total; i++) { //--- Forming the object name //--- Calculating coordinates //--- Set up a button //--- Setting up properties //--- Store the margins from the edge of the panel, coordinates and size //--- Initializing the gradient array //--- Store the object pointer } //--- return(true); }
Let us create two modes for this type of button group. To set this up, add a designated field and method to the class as shown in the code below. The default value will be false, which means that the mode allowing to have all buttons unpressed in a group is enabled. The default value true means that one button in a group will always be pressed.
class CButtonsGroup : public CElement { public: //--- The radio button mode bool m_radio_buttons_mode; //--- public: //--- Installing the radio button mode void RadioButtonsMode(const bool flag) { m_radio_buttons_mode=flag; } };
As in any other control, this class must contain a method for blocking the control. For this type of button group, it is important to ensure that when unblocking a previously pressed button it restores the appearance of its state.
class CButtonsGroup : public CElement { private: //--- Available/blocked bool m_buttons_group_state; //--- public: //--- General state of the button group (available/blocked) bool ButtonsGroupState(void) const { return(m_buttons_group_state); } void ButtonsGroupState(const bool state); }; //+------------------------------------------------------------------+ //| Changing the state of buttons | //+------------------------------------------------------------------+ void CButtonsGroup::ButtonsGroupState(const bool state) { m_buttons_group_state=state; //--- int buttons_total=ButtonsTotal(); for(int i=0; i<buttons_total; i++) { m_buttons[i].State(false); m_buttons[i].Color((state)? m_text_color : m_text_color_off); m_buttons[i].BackColor((state)? m_buttons_color[i]: m_back_color_off); m_buttons[i].BorderColor((state)? m_border_color : m_border_color_off); } //--- Press the button if it was pressed before blocking if(m_buttons_group_state) { if(m_selected_button_index!=WRONG_VALUE) { m_buttons_state[m_selected_button_index]=true; m_buttons[m_selected_button_index].Color(m_text_color_pressed); m_buttons[m_selected_button_index].BackColor(m_buttons_color_pressed[m_selected_button_index]); } } }
We need a method for toggling the button state when pressing. We will also require fields and methods for storing and getting a text and index of the highlighted button.
At the beginning of the method for toggling the button state in the code below there is a check for the number of buttons in a group. If it turns out that there are no buttons at all, a relevant message will be printed in the journal. The program will not leave the method. The program will carry on and at some point will encounter an error of exceeding the m_buttons_state[] array range. This means that the developer of the application must add at least one button to the group to avoid such an error. After the check, if there is at least one button, the program will adjust the passed index in case the array range has been exceeded. Then, the state of the button is changed by the specified index. After that, all buttons except the pressed one become unpressed (the relevant color is set up) in a loop considering the group mode. In other words, if the radio button mode is enabled when at least one button must be pressed at a time, then during every iteration a check is carried out for a condition of the equality of the passed index to the current index of the loop. If the mode is enabled when all buttons can be unpressed, then in addition to the check for the index, the current state of the button is checked. If the condition is met, then the pressed button flag is set irrespective of the mode. At the end of the method, in the class where the text and index of the highlighted button will be saved, its fields will get corresponding values by this flag. If there is not a pressed button, empty values are set ("" and WRONG_VALUE).
class CButtonsGroup : public CElement { private: //--- (1) Text and (2) index of the highlighted button string m_selected_button_text; int m_selected_button_index; //--- public: //--- Returns (1) the text and (2) index of the highlighted button string SelectedButtonText(void) const { return(m_selected_button_text); } int SelectedButtonIndex(void) const { return(m_selected_button_index); } //--- Toggles the button state by the specified index void SelectionButton(const int index); }; //+------------------------------------------------------------------+ //| Toggles the button state by the specified index | //+------------------------------------------------------------------+ void CButtonsGroup::SelectionButton(const int index) { //--- For checking for a pressed button in a group bool check_pressed_button=false; //--- Get the number of buttons int buttons_total=ButtonsTotal(); //--- If there is no button in a group, report if(buttons_total<1) { ::Print(__FUNCTION__," > This method is to be called " "if a group contains at least one button! Use the CButtonsGroup::AddButton() method"); } //--- Adjust the index value if the array range is exceeded int correct_index=(index>=buttons_total)? buttons_total-1 : (index<0)? 0 : index; //--- Change the button state for the opposite m_buttons_state[correct_index]=(m_buttons_state[correct_index])? false : true; //--- Iterate over a group of buttons for(int i=0; i<buttons_total; i++) { //--- A relevant check is carried out depending on the mode bool condition=(m_radio_buttons_mode)? (i==correct_index) : (i==correct_index && m_buttons_state[i]); //--- If the condition is met, make the button pressed if(condition) { if(m_radio_buttons_mode) m_buttons_state[i]=true; //--- There is a pressed button check_pressed_button=true; //--- Set a color m_buttons[i].Color(m_text_color_pressed); m_buttons[i].BackColor(m_buttons_color_pressed[i]); CElement::InitColorArray(m_buttons_color_pressed[i],m_buttons_color_pressed[i],m_buttons_total[i].m_buttons_color_array); } //--- If the condition is not met, make the button unpressed else { //--- Set the disabled state and colors m_buttons_state[i]=false; m_buttons[i].Color(m_text_color); m_buttons[i].BackColor(m_buttons_color[i]); CElement::InitColorArray(m_buttons_color[i],m_buttons_color_hover[i],m_buttons_total[i].m_buttons_color_array); } //--- Zero the normal state of the button m_buttons[i].State(false); } //--- If there is a pressed button, store its text and index m_selected_button_text =(check_pressed_button) ? m_buttons[correct_index].Description() : ""; m_selected_button_index =(check_pressed_button) ? correct_index : WRONG_VALUE; }
To handle the pressing of a group button, create the CButtonsGroup::OnClickButton() method. Similar to many other homonym methods, considered before, the following checks are carried out:
- for the name;
- for the identifier. To extract the identifier from the object name use the CButtonsGroup::IdFromObjectName() method. The code of the method is similar to the homonym methods considered before in other classes of controls and we will not discuss it here;
- for the current state (available/blocked).
If all checks have been passed and the program has not left the method, toggling the button state in the group will be carried out in the CButtonsGroup::SelectionButton() method considered before. At the end of this method, a message containing the data about the pressed button is sent to the stream of events. This message can be received in the handler of the custom class.
class CButtonsGroup : public CElement { private: //--- Handling the pressing of the button bool OnClickButton(const string clicked_object); //--- Getting the identifier from the button name int IdFromObjectName(const string object_name); }; //+------------------------------------------------------------------+ //| Chart event handler | //+------------------------------------------------------------------+ void CButtonsGroup::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Handling the left mouse button click event on the object if(id==CHARTEVENT_OBJECT_CLICK) { if(OnClickButton(sparam)) return; } } //+------------------------------------------------------------------+ //| Pressing a button in a group | //+------------------------------------------------------------------+ bool CButtonsGroup::OnClickButton(const string pressed_object) { //--- Leave, if the pressing was not on the menu item if(::StringFind(pressed_object,CElement::ProgramName()+"_buttons_",0)<0) return(false); //--- Get the identifier from the object name int id=IdFromObjectName(pressed_object); //--- Leave, if identifiers do not match if(id!=CElement::Id()) return(false); //--- For checking the index int check_index=WRONG_VALUE; //--- Check, if the pressing was on one of the buttons of this group int buttons_total=ButtonsTotal(); //--- Leave, if the buttons are blocked if(!m_buttons_group_state) { for(int i=0; i<buttons_total; i++) m_buttons[i].State(false); //--- return(false); } //--- If the pressing took place, store the index for(int i=0; i<buttons_total; i++) { if(m_buttons[i].Name()==pressed_object) { check_index=i; break; } } //--- Leave, if the button of this group was not pressed if(check_index==WRONG_VALUE) return(false); //--- Toggle the button state SelectionButton(check_index); //--- Send a signal about it ::EventChartCustom(m_chart_id,ON_CLICK_BUTTON,CElement::Id(),m_selected_button_index,m_selected_button_text); return(true); }
Now, we need to set up the reaction of the group buttons to the mouse cursor hovering over them and the left mouse click. For that, let us write the CButtonsGroup::CheckPressedOverButton() method, which will be called in the control handler during handling the CHARTEVENT_MOUSE_MOVE event. Before the method is called, the following checks will be carried out: (1) if the control is visible, (2) if it is available, (3) if the form is available and (4) if the left mouse button is pressed.
class CButtonsGroup : public CElement { private: //--- Checking the pressed left mouse button over the group buttons void CheckPressedOverButton(void); }; //+------------------------------------------------------------------+ //| Chart event handler | //+------------------------------------------------------------------+ void CButtonsGroup::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Handling the cursor movement event if(id==CHARTEVENT_MOUSE_MOVE) { //--- Leave, if the control is hidden if(!CElement::IsVisible()) return; //--- Leave, if the buttons are blocked if(!m_buttons_group_state) return; //--- Define the focus int x=(int)lparam; int y=(int)dparam; int buttons_total=ButtonsTotal(); for(int i=0; i<buttons_total; i++) { m_buttons[i].MouseFocus(x>m_buttons[i].X() && x<m_buttons[i].X2() && y>m_buttons[i].Y() && y<m_buttons[i].Y2()); } //--- Leave, if the form is blocked if(m_wnd.IsLocked()) return; //--- Leave, if the mouse button is not pressed if(sparam!="1") return; //--- Checking the pressed left mouse button over the group buttons CheckPressedOverButton(); return; } } //+------------------------------------------------------------------+ //| Checking the pressed left mouse button over the group buttons | //+------------------------------------------------------------------+ void CButtonsGroup::CheckPressedOverButton(void) { int buttons_total=ButtonsTotal(); //--- Set the color depending on the location of the left mouse button press for(int i=0; i<buttons_total; i++) { //--- If there is a focus, then the color of the pressed button if(m_buttons[i].MouseFocus()) m_buttons[i].BackColor(m_buttons_color_pressed[i]); //--- If there is no focus, then... else { //--- ...if a group button is not pressed, assign the background color if(!m_buttons_state[i]) m_buttons[i].BackColor(m_buttons_color[i]); } } }
Everything is ready for testing a group of simple buttons in the test application. Make a copy of the EA that we have previously tested. Remove all controls except the main menu with its context menus. We will use this program for testing various button groups in this article.
Create an instance of the CButtonsGroup class of the button group in the custom class. Declare the CProgram::CreateButtonsGroup1() method for its creation and margins from the edge of the form.
class CProgram : public CWndEvents { private: //--- Group of simple buttons CButtonsGroup m_buttons_group1; //--- private: //--- Group of simple buttons #define BUTTONS_GROUP1_GAP_X (7) #define BUTTONS_GROUP1_GAP_Y (50) bool CreateButtonsGroup1(void); };
Create a group of four buttons. Arrange them horizontally. For this example, we will make two buttons red and two buttons blue. The implementation of the CProgram::CreateButtonsGroup1() method is shown in the code below. If you need one button to be highlighted straight after the button group was created, use the CButtonsGroup::SelectionButton() public method.
//+------------------------------------------------------------------+ //| Creates a group of simple buttons | //+------------------------------------------------------------------+ bool CProgram::CreateButtonsGroup1(void) { //--- Store the window pointer m_buttons_group1.WindowPointer(m_window); //--- Coordinates int x =m_window.X()+BUTTONS_GROUP1_GAP_X; int y =m_window.Y()+BUTTONS_GROUP1_GAP_Y; //--- Properties int buttons_x_gap[] ={0,72,144,216}; string buttons_text[] ={"BUTTON 1","BUTTON 2","BUTTON 3","BUTTON 4"}; int buttons_width[] ={70,70,70,70}; color buttons_color[] ={C'195,0,0',C'195,0,0',clrRoyalBlue,clrRoyalBlue}; color buttons_color_hover[] ={C'255,51,51',C'255,51,51',C'85,170,255',C'85,170,255'}; color buttons_color_pressed[] ={C'135,0,0',C'135,0,0',C'50,100,135',C'50,100,135'}; //--- Set properties m_buttons_group1.TextColor(clrWhite); m_buttons_group1.TextColorPressed(clrGold); //--- Add four buttons to a group for(int i=0; i<4; i++) m_buttons_group1.AddButton(buttons_x_gap[i],0,buttons_text[i],buttons_width[i], buttons_color[i],buttons_color_hover[i],buttons_color_pressed[i]); //--- Create a group of buttons if(!m_buttons_group1.CreateButtonsGroup(m_chart_id,m_subwin,x,y)) return(false); //--- Highlight the second button in the group m_buttons_group1.SelectionButton(1); //--- Add an object to the common array of object groups CWndContainer::AddToElementsArray(0,m_buttons_group1); return(true); }
After compiling files and loading the program on the chart, you should get the result as shown in the screenshot below.
Fig. 2. Test of the group of simple buttons control.
We have finished with the first group of buttons. The complete version of the CButtonsGroup class can be downloaded at the end of this article. The next control to consider is a group of radio buttons.
Developing the Class for Creating Groups of Radio Buttons
Create the RadioButtons.mqh file with the CRadioButtons class which must contain standard virtual methods and class members for storing and getting the form pointer. You can see examples in the classes of other controls above. Include the RadioButtons.mqh file in the library (WndContainer.mqh).
Each radio item will be composed of thee primitive objects:
- background;
- icon;
- text label.
Fig. 3. Compound parts of radio buttons.
Unlike a group of simple buttons, a group of radio buttons will have only one mode. This is because in this control all the buttons cannot be unpressed simultaneously. One button in this group will always be pressed. The background color of the group is usually the same as the background of the form to which the group is attached. Essentially, it is used for identifying the focus and tracking the pressing of a radio button (higher priority). In this version, we can set up the color change only for the text label when the cursor gets in the area of the button background and leaves it. The lists of unique and common properties of a radio button are different to the lists of properties of a simple button as shown below.
Common properties:
- Color of the background and frame.
- Text color.
- Height.
- Button icons for the (1) active, (2) disabled and (3) blocked state.
- Priority of the left mouse click.
Unique properties:
- Text.
- Width.
- State. Only one button in the group can be pressed.
- Margins. Similar to simple buttons, radio buttons can be arranged in any order (mosaic, horizontally, vertically, etc.).
- Gradients for text labels.
You can see what it looks like in the code of the CRadioButtons class below. The sizing of the unique properties arrays and filling with values are carried out with the CRadioButtons::AddButton() public method before creating the control in the custom class. If no buttons were added, then the creation of the graphical interface will be terminated. This has been shown in the development of the class for creating a group of simple buttons and we are not going to spend more time on it.
//+------------------------------------------------------------------+ //| Class for creating groups of radio buttons | //+------------------------------------------------------------------+ class CRadioButtons : public CElement { private: //--- Gradients of text labels struct LabelsGradients { color m_labels_color_array[]; }; LabelsGradients m_labels_total[]; //--- Button properties: // (1) Color and (2) priority of the background of the left mouse click color m_area_color; int m_area_zorder; //--- Arrays for unique properties of buttons bool m_buttons_state[]; int m_buttons_x_gap[]; int m_buttons_y_gap[]; int m_buttons_width[]; string m_buttons_text[]; //--- Height of buttons int m_button_y_size; //--- Icons for buttons in the active, disabled and blocked state string m_icon_file_on; string m_icon_file_off; string m_icon_file_on_locked; string m_icon_file_off_locked; //--- Text color color m_text_color; color m_text_color_off; color m_text_color_hover; //--- Priority of left mouse click int m_buttons_zorder; //--- public: //--- Setting up icons for the button in the active, disabled and blocked state void IconFileOn(const string file_path) { m_icon_file_on=file_path; } void IconFileOff(const string file_path) { m_icon_file_off=file_path; } void IconFileOnLocked(const string file_path) { m_icon_file_on_locked=file_path; } void IconFileOffLocked(const string file_path) { m_icon_file_off_locked=file_path; } //--- (1) Background color, (2) text color void AreaColor(const color clr) { m_area_color=clr; } void TextColor(const color clr) { m_text_color=clr; } void TextColorOff(const color clr) { m_text_color_off=clr; } void TextColorHover(const color clr) { m_text_color_hover=clr; } //--- Adds a button with specified properties before creation void AddButton(const int x_gap,const int y_gap,const string text,const int width); };
Then, we need to create arrays of instances of primitive object classes and methods for their creation. Similar to a group of simple buttons, radio buttons will be created in a loop. However, here the loop will be located in the main method and the index, which will participate in forming the name of each object, will be passed to the methods of creating those objects.
class CRadioButtons : public CElement { private: //--- Objects for creating a button CRectLabel m_area[]; CBmpLabel m_icon[]; CLabel m_label[]; //--- public: //--- Methods for creating a button bool CreateRadioButtons(const long chart_id,const int window,const int x,const int y); //--- private: bool CreateArea(const int index); bool CreateRadio(const int index); bool CreateLabel(const int index); //--- public: //--- Number of buttons int RadioButtonsTotal(void) const { return(::ArraySize(m_icon)); } }; //+------------------------------------------------------------------+ //| Creates a group of the button objects | //+------------------------------------------------------------------+ bool CRadioButtons::CreateRadioButtons(const long chart_id,const int window,const int x,const int y) { //--- Leave, if there is no form pointer if(::CheckPointer(m_wnd)==POINTER_INVALID) { ::Print(__FUNCTION__," > Before creating a group of radio buttons, the class must be passed " "the form pointer: CButtonsGroup::WindowPointer(CWindow &object)"); return(false); } //--- Initializing variables m_id =m_wnd.LastId()+1; m_chart_id =chart_id; m_subwin =window; m_x =x; m_y =y; //--- Get the number of buttons in the group int radio_buttons_total=RadioButtonsTotal(); //--- If there is no button in a group, report if(radio_buttons_total<1) { ::Print(__FUNCTION__," > This method is to be called " "if a group contains at least one button! Use the CRadioButtons::AddButton() method"); return(false); } //--- Set up a group of buttons for(int i=0; i<radio_buttons_total; i++) { CreateArea(i); CreateRadio(i); CreateLabel(i); //--- Zeroing the focus m_area[i].MouseFocus(false); } //--- Hide the element if it is a dialog window or it is minimized if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized()) Hide(); //--- return(true); }
This class must also contain methods for changing the state of a control (available/blocked) and toggling the button state by the specified index. Here, these methods are slightly simpler than in the CButtonsGroup class because a group of radio items has only one mode. You can study these methods in the files attached to this article. In addition to that, this class requires methods for getting the text and index of the highlighted button like in the class of simple buttons.
class CButtonsGroup : public CElement { private: //--- (1) Text and (2) index of the highlighted button string m_selected_button_text; int m_selected_button_index; //--- Available/blocked bool m_buttons_group_state; //--- public: //--- General state of the button group (available/blocked) bool ButtonsGroupState(void) const { return(m_buttons_group_state); } void ButtonsGroupState(const bool state); //--- Returns (1) the text and (2) index of the highlighted button string SelectedButtonText(void) const { return(m_selected_button_text); } int SelectedButtonIndex(void) const { return(m_selected_button_index); } //--- Toggles the button state by the specified index void SelectionButton(const int index); };
We will use the new identifier ON_CLICK_LABEL for sending a message about the pressing of controls which look like a text label on the form. Add this to the Defines.mqh file:
#define ON_CLICK_LABEL (10) // Pressing of the text label
The implementation of the method for handling of the pressing of the group buttons is shown below. After passing the following checks (1) for belonging to the control type, (2) for the identifier and (3) for the availability, the index of the pressed button will be identified by the exact name in a loop. If a button of this group was pressed and this button is not currently highlighted, toggling will be carried out and a message will be sent, which can be received in the custom class.
class CButtonsGroup : public CElement { private: //--- Handling the pressing of the button bool OnClickButton(const string pressed_object); //--- Getting the identifier from the name of the radio button int IdFromObjectName(const string object_name); }; //+------------------------------------------------------------------+ //| Chart event handler | //+------------------------------------------------------------------+ void CRadioButtons::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Handling the left mouse button click event on the object if(id==CHARTEVENT_OBJECT_CLICK) { //--- Toggle the button state if(OnClickButton(sparam)) return; } } //+------------------------------------------------------------------+ //| Pressing of a radio button | //+------------------------------------------------------------------+ bool CRadioButtons::OnClickButton(const string pressed_object) { //--- Leave, if the pressing was not on the menu item if(::StringFind(pressed_object,CElement::ProgramName()+"_radio_area_",0)<0) return(false); //--- Get the identifier and index from the object name int id=IdFromObjectName(pressed_object); //--- Leave, if the pressing was not of the item to which this context menu is attached if(id!=CElement::Id()) return(false); //--- For checking the index int check_index=WRONG_VALUE; //--- Leave, if the buttons are blocked if(!m_radio_buttons_state) return(false); //--- If the pressing took place, store the index int radio_buttons_total=RadioButtonsTotal(); for(int i=0; i<radio_buttons_total; i++) { if(m_area[i].Name()==pressed_object) { check_index=i; break; } } //--- Leave, if there was no pressing of a button in this group or // if it is an already highlighted radio button if(check_index==WRONG_VALUE || check_index==m_selected_button_index) return(false); //--- Toggle the button state SelectionRadioButton(check_index); //--- Send a signal about it ::EventChartCustom(m_chart_id,ON_CLICK_LABEL,CElement::Id(),check_index,m_selected_button_text); return(true); }
Now, everything is ready for testing. Add four groups of four radio buttons (CRadioButtons)and a group of simple buttons (CButtonsGroup) to the EA which was used for testing a group of simple buttons.
class CProgram : public CWndEvents { private: //--- Group of radio buttons 1 CRadioButtons m_radio_buttons1; //--- Group of simple buttons 2 CButtonsGroup m_buttons_group2; //--- Groups of radio buttons 2,3,4 CRadioButtons m_radio_buttons2; CRadioButtons m_radio_buttons3; CRadioButtons m_radio_buttons4; //--- private: //--- Group of radio buttons 1 #define RADIO_BUTTONS1_GAP_X (7) #define RADIO_BUTTONS1_GAP_Y (75) bool CreateRadioButtons1(); //--- Group of simple buttons 2 #define BUTTONS_GROUP2_GAP_X (7) #define BUTTONS_GROUP2_GAP_Y (100) bool CreateButtonsGroup2(void); //--- Groups of radio buttons 2,3,4 #define RADIO_BUTTONS2_GAP_X (7) #define RADIO_BUTTONS2_GAP_Y (125) bool CreateRadioButtons2(); #define RADIO_BUTTONS3_GAP_X (105) #define RADIO_BUTTONS3_GAP_Y (125) bool CreateRadioButtons3(); #define RADIO_BUTTONS4_GAP_X (203) #define RADIO_BUTTONS4_GAP_Y (125) bool CreateRadioButtons4(); }; //+------------------------------------------------------------------+ //| Creates the trading panel | //+------------------------------------------------------------------+ bool CProgram::CreateTradePanel(void) { //--- Creating a form for controls //--- Creating controls: // Main menu //--- Context menus //--- Group of simple buttons 1 //--- Group of radio buttons 1 if(!CreateRadioButtons1()) return(false); //--- Group of simple buttons 2 if(!CreateButtonsGroup2()) return(false); //--- Groups of radio buttons 2,3,4 if(!CreateRadioButtons2()) return(false); if(!CreateRadioButtons3()) return(false); if(!CreateRadioButtons4()) return(false); //--- Redrawing of the chart m_chart.Redraw(); return(true); }
We will used the implementation of the method for one of them as an example. The rest will differ only in parameter values.
//+------------------------------------------------------------------+ //| Creates group of radio buttons 1 | //+------------------------------------------------------------------+ bool CProgram::CreateRadioButtons1(void) { //--- Pass the panel object m_radio_buttons1.WindowPointer(m_window); //--- Coordinates int x =m_window.X()+RADIO_BUTTONS1_GAP_X; int y =m_window.Y()+RADIO_BUTTONS1_GAP_Y; //--- Properties int buttons_x_offset[] ={0,98,196}; int buttons_y_offset[] ={0,0,0}; string buttons_text[] ={"Radio Button 1","Radio Button 2","Radio Button 3"}; int buttons_width[] ={92,92,92}; //--- for(int i=0; i<3; i++) m_radio_buttons1.AddButton(buttons_x_offset[i],buttons_y_offset[i],buttons_text[i],buttons_width[i]); //--- Create a group of buttons if(!m_radio_buttons1.CreateRadioButtons(m_chart_id,m_subwin,x,y)) return(false); //--- Highlight the second button in the group m_radio_buttons1.SelectedRadioButton(1); //--- Block radio buttons m_radio_buttons1.RadioButtonsState(false); //--- Add an object to the common array of object groups CWndContainer::AddToElementsArray(0,m_radio_buttons1); return(true); }
Let us create two buttons in the additional (second) group of buttons of the CButtonsGroup type. For the sake of illustration, let us arrange that the second button of this group will block the first groups of simple and radio buttons and the first one will make them available.
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Pressing of the button event if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { ::Print("id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam); //--- If the identifier of the second group or simple buttons and // the text of highlighted button in this group match the string parameter of the message if(lparam==m_buttons_group2.Id() && sparam==m_buttons_group2.SelectedButtonText()) { //--- If this is the index of the first button, unblock specified controls if((int)dparam==0) { m_buttons_group1.ButtonsGroupState(true); m_radio_buttons1.RadioButtonsState(true); } //--- If this is the index of the second button, block specified controls else { m_buttons_group1.ButtonsGroupState(false); m_radio_buttons1.RadioButtonsState(false); } } return; } }
Compile the files and load the program on to the chart. At this stage of development, you should get the result as shown in the screenshot below.
Fig. 4. Test of the radio button control.
We have finished the development of the class for creating the group of radio buttons control. You can download a complete version in the files attached to this article.
Developing the Class for Creating Groups of Icon Buttons
We have previously created the CIconButton class for creating the icon button control. Now, we are going to implement a control that will allow us to create a group of such buttons. Create the IconButtonsGroup.mqh file with the CIconButtonsGroup class where you can declare and implement standard virtual methods in the Controls folder containing previously created files with the classes of controls. Include this file in the WndContainer.mqh file of the library.
We will not describe this in detail as the CIconButtonsGroup class is essentially a composition of the methods that have been already considered in the CButtonsGroup and CRadioButtons class. Similar to the CRadioButtons class of radio buttons, the objects of the button group will be created with private methods in a loop of the main method for creating the control. The only parameter of these methods is the index that will be used for forming the names of graphical objects. We can arrange that the buttons of this group will change their background color and text when the cursor is hovering over them.
Methods concerning the handling of events have already been discussed in this article. They are the same in this type of button group so we will show only the content of the CIconButtonsGroup class in the code below. The implementation of the methods that are outside of the class body can be found in the files attached to this article.
//+------------------------------------------------------------------+ //| Class for creating a group of icon buttons | //+------------------------------------------------------------------+ class CIconButtonsGroup : public CElement { private: //--- Pointer to the form to which the control is attached CWindow *m_wnd; //--- Objects for creating a button CButton m_buttons[]; CBmpLabel m_icons[]; CLabel m_labels[]; //--- Gradients of text labels struct IconButtonsGradients { color m_back_color_array[]; color m_label_color_array[]; }; IconButtonsGradients m_icon_buttons_total[]; //--- Button properties: // Arrays for unique properties of buttons bool m_buttons_state[]; int m_buttons_x_gap[]; int m_buttons_y_gap[]; string m_buttons_text[]; int m_buttons_width[]; string m_icon_file_on[]; string m_icon_file_off[]; //--- Height of buttons int m_buttons_y_size; //--- Background color in different modes color m_back_color; color m_back_color_off; color m_back_color_hover; color m_back_color_pressed; //--- Frame color color m_border_color; color m_border_color_off; //--- Icon margins int m_icon_x_gap; int m_icon_y_gap; //--- Text and text label margins int m_label_x_gap; int m_label_y_gap; //--- Text label color in different modes color m_label_color; color m_label_color_off; color m_label_color_hover; color m_label_color_pressed; //--- (1) Text and (2) index of the highlighted button string m_selected_button_text; int m_selected_button_index; //--- General priority of unclickable objects int m_zorder; //--- Priority of left mouse click int m_buttons_zorder; //--- Available/blocked bool m_icon_buttons_state; //--- public: CIconButtonsGroup(void); ~CIconButtonsGroup(void); //--- Methods for creating a button bool CreateIconButtonsGroup(const long chart_id,const int window,const int x,const int y); //--- private: bool CreateButton(const int index); bool CreateIcon(const int index); bool CreateLabel(const int index); //--- public: //--- (1) Stores the form pointer, (2) button height, (3) number of buttons, // (4) general state of the button (available/blocked) void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } void ButtonsYSize(const int y_size) { m_buttons_y_size=y_size; } int IconButtonsTotal(void) const { return(::ArraySize(m_icons)); } bool IconButtonsState(void) const { return(m_icon_buttons_state); } void IconButtonsState(const bool state); //--- Button background colors void BackColor(const color clr) { m_back_color=clr; } void BackColorOff(const color clr) { m_back_color_off=clr; } void BackColorHover(const color clr) { m_back_color_hover=clr; } void BackColorPressed(const color clr) { m_back_color_pressed=clr; } //--- Icon margins void IconXGap(const int x_gap) { m_icon_x_gap=x_gap; } void IconYGap(const int y_gap) { m_icon_y_gap=y_gap; } //--- Text label margins void LabelXGap(const int x_gap) { m_label_x_gap=x_gap; } void LabelYGap(const int y_gap) { m_label_y_gap=y_gap; } //--- Returns (1) the text and (2) index of the highlighted button string SelectedButtonText(void) const { return(m_selected_button_text); } int SelectedButtonIndex(void) const { return(m_selected_button_index); } //--- Toggles the state of a radio button by the specified index void SelectedRadioButton(const int index); //--- Adds a button with specified properties before creation void AddButton(const int x_gap,const int y_gap,const string text, const int width,const string icon_file_on,const string icon_file_off); //--- Changing the color void ChangeObjectsColor(void); //--- 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 control 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) Setting, (2) resetting priorities of left mouse button click virtual void SetZorders(void); virtual void ResetZorders(void); //--- private: //--- Handling the pressing of the button bool OnClickButton(const string pressed_object); //--- Checking the pressed left mouse button over the group buttons void CheckPressedOverButton(void); //--- Getting the identifier from the name of the radio button int IdFromObjectName(const string object_name); };
Let us test this control. As an example, create a group of icon buttons on the form of the same EA where we previously tested groups of buttons of the CButtonsGroup and CRadioButtons type. For that, add the lines of the code to the custom class as shown in the code below.
class CProgram : public CWndEvents { private: //--- Group of icon buttons 1 CIconButtonsGroup m_icon_buttons_group1; //--- private: //--- Group of icon buttons 1 #define IBUTTONS_GROUP1_GAP_X (7) #define IBUTTONS_GROUP1_GAP_Y (190) bool CreateIconButtonsGroup1(void); }; //+------------------------------------------------------------------+ //| Creates the trading panel | //+------------------------------------------------------------------+ bool CProgram::CreateTradePanel(void) { //--- Creating a form for controls //--- Creating controls: // Main menu //--- Context menus //--- Group of simple buttons 1 //--- Group of radio buttons 1 //--- Group of simple buttons 2 //--- Groups of radio buttons 2,3,4 //--- Group of icon buttons 1 if(!CreateIconButtonsGroup1()) return(false); //--- Redrawing of the chart m_chart.Redraw(); return(true); }
Compile the files and load the program to the chart. You should get the result as shown in the screenshot below.
Fig. 5. Test of the group of icon buttons.
The development of the class for creating the group of icon buttons control is completed. You can download a complete version in the files attached to this article.
Conclusion
This is the end of the third part of the series about developing the library for creating graphical interfaces in the MetaTrader trading terminals. Currently, the library structure looks as shown in the schematic below.
Fig. 6. Library structure at the current stage of development.
In the next (fourth) part of the series, we will continue developing the library. We will consider the following topics:
- Multi-window mode.
- System of managing priorities of the left mouse click on graphical objects.
- The status bar and tooltips informational interface elements.
The archives below with library files at the current stage of development, pictures and files of the programs considered in this article can be downloaded for tests in the MetaTrader terminals. 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 (chapters of the third part:
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/2298
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
...
Is there any way to fix it ?
P.S. the curious thing is, that all works fine on MT5. Just MT4 has this Issue when using as Indicator. As Expert it works also fine.
Hi,
I have done a quick´n´dirty solution which works for me very well. My way was to check the left button pressed bit separate at first in OnEvent and if is set to write a bool true in a helper. After that the regular if statemenet extended with helper == true , and setting inside the if just resetting the helper. Works nearly perfect. In some cases to fast clicks are not mentioned, but better slower clicking except having double results :)
BTW: would be nice, if someone could update all your current Libraries to english. Your newest one f.e. article 05 and so on are only in russian.
Hi,
I have a question,
how do you make the black part around your icons transparent?
thk
Hi.
I just downloaded the files from the article3
When I compiled it.. I got the following error
CContextMenu *GetContextMenuPointer(void) const { return(::GetPointer(m_drop_menu));
File = splitButton.mqh.
Does anyone know how to fix it?
I am trying to create a simple Button.
thanks
Hi.
I just downloaded the files from the article3
When I compiled it.. I got the following error
CContextMenu *GetContextMenuPointer(void) const { return(::GetPointer(m_drop_menu));
File = splitButton.mqh.
Does anyone know how to fix it?
I am trying to create a simple Button.
thanks
Delete "const" is OK
CContextMenu *GetContextMenuPointer(void) { return(::GetPointer(m_drop_menu));