Graphical Interfaces X: Updates for Easy And Fast Library (Build 3)
Contents
Introduction
The first article Graphical Interfaces I: Preparation of the Library Structure (Chapter 1) considers in detail what this library is for. A complete list of links to the articles of the first part is 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.
The next version of the Easy And Fast library (version 3) is presented in this article. Fixed certain flaws and added new features. More details further in the article.
Starting from this version, the library will only be developed for the MetaTrader 5 platform. This is due to certain fundamental architectural differences and limitations in the MetaTrader 4. However, if there is an urgent need for support of the library for an earlier version of the platform, it is possible to issue a request in the Freelance service addressed to the author or any other developer, willing and able to do the job.
Updates
1. Until now, the MQL applications in the previous articles demonstrated how to implement a graphical interface in the indicator subwindow. Of course, this approach is sufficient for some simple applications. But it would also be good to have the ability to create a graphical interface in a subwindow for an "Expert" type program. That way, it would be possible to create fully fledged trading panels, completely separated from the main chart window. Working in this mode will be easier: the price chart and any important data on the chart would always remain open and uncluttered by the graphical interface of the application. Implementation of this idea within the Easy And Fast library will be described further.
The task was to make the "Expert in subwindow" mode be activated by including the resource with a placeholder indicator (SubWindow.ex5) in the main file of the MQL application. The placeholder is just a subwindow without any series calculations. But since there is currently no way to know if the indicator is included as a resource using the MQL means, temporarily add the directive with the EXPERT_IN_SUBWINDOW constant identifier to the Defines.mqh file. It is required to eliminate the logging of error that occurs when trying to get the handle of an indicator that is not available on the specified directory.
The default value is false, which means that the mode is disabled and the graphical interface will be created in the main chart window (see the code below).
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ //--- "Expert in subwindow" mode #define EXPERT_IN_SUBWINDOW false ...
If it is necessary to create the graphical interface in a subwindow, set the value to true and include the resource with the placeholder indicator to the mail file of the MQL application.
//+------------------------------------------------------------------+ //| TestLibrary01.mq5 | //| Copyright 2016, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2016, MetaQuotes Software Corp." #property link "http://www.mql5.com" //--- Including indicator for the "Expert in subwindow" mode #resource \\Indicators\\SubWindow.ex5 ...
Next, consider the SubWindow.ex5 indicator and how it is linked to the EA. The full code of the SubWindow.ex5 indicator has been provided in the listing below. The program properties specify that the indicator is a subwindow of the chart, the number of buffers and series is zero, the maximum and minimum of the vertical scale are also zero.
The indicator and expert will exchange messages. The thing is, the indicator "does not know" which modes for plotting the graphical interface are selected in the expert. It needs to be passed a message, if developer had decided to make the subwindow have a fixed height, as well as the height values in pixels. Another ON_SUBWINDOW_CHANGE_HEIGHT event identifier will be required for creating the message. This identifier should also be placed in the Defines.mqh file as well, so that it would be available in applications, which use this library for creating graphical interfaces.
In order to understand when the EA needs to generate the event for changing the subwindow height: after a successful initialization of the indicator, during the first automatic call to the ::OnCalculate() function (when the prev_calculated argument is zero), the message for the EA will be passed. To unambiguously determine that the message came from the SubWindow.ex5 indicator, apart from the ON_SUBWINDOW_CHANGE_HEIGHT event identifier, the name of the program will be sent as a string parameter (sparam). The message will be traced in the handler of the CWindow class. If all conditions are met there, the indicator will be sent a response with the same (1) identifier(ON_SUBWINDOW_CHANGE_HEIGHT), (2) height value in long parameter of the event(lparam) and (3) the expert name. Once this message is received, it will be processed in the ::OnChartEvent() function. After checking the name, height of the subwindow is set according to the passed value.
//+------------------------------------------------------------------+ //| SubWindow.mq5 | //| Copyright 2016, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2016, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property indicator_separate_window #property indicator_plots 0 #property indicator_buffers 0 #property indicator_minimum 0.0 #property indicator_maximum 0.0 //--- Program name #define PROGRAM_NAME ::MQLInfoString(MQL_PROGRAM_NAME) //--- Identifier of the event for changing the expert subwindow height #define ON_SUBWINDOW_CHANGE_HEIGHT (25) //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit(void) { //--- Short name of the indicator ::IndicatorSetString(INDICATOR_SHORTNAME,PROGRAM_NAME); //--- Initialization successful return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { //--- If the initialization succeeded if(prev_calculated<1) //--- Send the message to the expert to get the subwindow size from it ::EventChartCustom(0,ON_SUBWINDOW_CHANGE_HEIGHT,0,0.0,PROGRAM_NAME); u//--- return(rates_total); } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Handling the event of changing the expert subwindow height if(id==CHARTEVENT_CUSTOM+ON_SUBWINDOW_CHANGE_HEIGHT) { //--- Accept messages only from the expert name if(sparam==PROGRAM_NAME) return; //--- Change the subwindow height ::IndicatorSetInteger(INDICATOR_HEIGHT,(int)lparam); } } //+------------------------------------------------------------------+
A block of code should be inserted to the event handler of the CWindow class, as shown in the listing below. Once an event with the ON_SUBWINDOW_CHANGE_HEIGHT identifier arrives, multiple checks will need to be passed. The program leaves the method if:
- the message was sent by the expert for the indicator;
- this program is not an expert;
- the mode of the expert subwindow fixed height is not set.
//+------------------------------------------------------------------+ //| Chart event handler | //+------------------------------------------------------------------+ void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { ... //--- Handling the event of changing the expert subwindow height if(id==CHARTEVENT_CUSTOM+ON_SUBWINDOW_CHANGE_HEIGHT) { //--- Leave, if the message was from the expert if(sparam==PROGRAM_NAME) return; //--- Leave, if this program is not the expert if(CElement::ProgramType()!=PROGRAM_EXPERT) return; //--- Leave, if the mode of the sub-window fixed height is not set if(!m_height_subwindow_mode) return; //--- Calculate and change the subwindow height m_subwindow_height=(m_is_minimized)? m_caption_height+3 : m_bg_full_height+3; ChangeSubwindowHeight(m_subwindow_height); return; } }
If all checks are passed, then the height for the subwindow is calculated with the consideration of the current state of the window (minimized/maximized). After that, the CWindow::ChangeSubwindowHeight() method is called, which had also been slightly modified (see the code below). Its purpose is that, if the program type is «Expert», then a message is generated for the SubWindow.ex5 indicator.
//+------------------------------------------------------------------+ //| Changes the height of the indicator sub-window | //+------------------------------------------------------------------+ void CWindow::ChangeSubwindowHeight(const int height) { //--- If the graphical interface is not in subwindow or the program is of "Script" type if(CElement::m_subwin<=0 || CElement::m_program_type==PROGRAM_SCRIPT) return; //--- If the subwindow height needs to be changed if(height>0) { //--- If the program is of the indicator type if(CElement::m_program_type==PROGRAM_INDICATOR) { if(!::IndicatorSetInteger(INDICATOR_HEIGHT,height)) ::Print(__FUNCTION__," > Failed to change the height of indicator subwindow! Error code: ",::GetLastError()); } //--- If the program is of the "Expert" type else { //--- Send the message to the SubWindow.ex5 indicator, informing that the window sizes must be changed ::EventChartCustom(m_chart_id,ON_SUBWINDOW_CHANGE_HEIGHT,(long)height,0,PROGRAM_NAME); } } }
The engine of the library (the CWndEvents class) also requires certain additions to be made in order to determine, check and adjust the subwindow number, where the graphical interface of the MQL application of the "Expert" type is to be placed. The code for the "Expert in subwindow" mode should be added to the CWndEvents::DetermineSubwindow() method. The code listing below shows the shortened version of this method. Entry to the block is performed on the condition that the program type is "Expert". Next comes the check of whether the "Expert in subwindow" mode is enabled. If enabled, get the handle of the SubWindow.ex5 indicator. And if there were no problems with that, then the current number of subwindows in the chart window is determined first. The obtained value defines the number of the SubWindow.ex5 indicator subwindow, which is set by the ::ChartIndicatorAdd() function. Then, if there were no errors when setting the subwindow, it stores (1) the number of expert subwindow, (2) the current number of subwindows and (3) the short name of the SubWindow.ex5 indicator.
//+------------------------------------------------------------------+ //| Class for event handling | //+------------------------------------------------------------------+ class CWndEvents : public CWndContainer { protected: //--- Handle of the expert subwindow int m_subwindow_handle; //--- Name of the expert subwindow string m_subwindow_shortname; //--- The number of subwindows on the chart after setting the expert subwindow int m_subwindows_total; }; //+------------------------------------------------------------------+ //| Identifying the sub-window number | //+------------------------------------------------------------------+ void CWndEvents::DetermineSubwindow(void) { //--- Leave, if the program type is "Script" //--- Reset the last error //--- If the program type is "Expert" if(PROGRAM_TYPE==PROGRAM_EXPERT) { //--- Leave, if the graphical interface of the expert is required in the main window if(!EXPERT_IN_SUBWINDOW) return; //--- Get the handle of the placeholder indicator (empty subwindow) m_subwindow_handle=iCustom(Symbol(),Period(),"::Indicators\\SubWindow.ex5"); //--- If there is no such indicator, report the error to the log if(m_subwindow_handle==INVALID_HANDLE) ::Print(__FUNCTION__," > Error getting the indicator handle in the directory ::Indicators\\SubWindow.ex5 !"); //--- If the handle is obtained, then the indicator exists, included in the application as a resource, // and this means that the graphical interface of the application must be placed in the subwindow. else { //--- Get the number of subwindows on the chart int subwindows_total=(int)::ChartGetInteger(m_chart_id,CHART_WINDOWS_TOTAL); //--- Set the subwindow for the graphical interface of the expert if(::ChartIndicatorAdd(m_chart_id,subwindows_total,m_subwindow_handle)) { //--- Store the subwindow number and the current number of subwindows on the chart m_subwin =subwindows_total; m_subwindows_total =subwindows_total+1; //--- Get and store the short name of the expert subwindow m_subwindow_shortname=::ChartIndicatorName(m_chart_id,m_subwin,0); } //--- If the subwindow was not set else ::Print(__FUNCTION__," > Error setting the expert subwindow! Error code: ",::GetLastError()); } u//--- return; } //--- Identifying the indicator window //--- Leave, if failed to identify the number //--- If this is not the main window of the chart ... }
It is also necessary to make sure than the number of the expert subwindow gets adjusted for correct operation of its graphical interface when adding and removing other indicators in the chart subwindows. In addition, let us make it so that if the user deletes the expert subwindow, the expert gets removed as well, leaving a log message in the "Experts" tab of the "Toolbox" window, indicating the reason for removal from the chart. The CWndEvents::CheckExpertSubwindowNumber() method has been implemented for that purpose. Admission to this method is carried out on the condition that the program has a type of "Expert". If the check is passed, then the number of subwindows in the chart window will be calculated. If it turns out that the number of subwindows had not changed, the program leaves the method.
Then it is necessary to find the expert subwindow in a cycle, and if it exists — check if its subwindow number had changed. The subwindow number could have been changed due to adding or removing an indicator which was also located in its separate subwindow. If the subwindow number has changed, it is necessary to store its number and update the value in all controls of the main window.
If the subwindow is not found, there can be only one reason: it has been deleted. In this case, the expert will also be removed from the chart.
class CWndEvents : public CWndContainer { private: //--- Check and update the expert subwindow number void CheckExpertSubwindowNumber(void); }; //+------------------------------------------------------------------+ //| Check and update the expert subwindow number | //+------------------------------------------------------------------+ void CWndEvents::CheckExpertSubwindowNumber(void) { //--- Leave, if this is not expert if(PROGRAM_TYPE!=PROGRAM_EXPERT) return; //--- Get the number of subwindows on the chart int subwindows_total=(int)::ChartGetInteger(m_chart_id,CHART_WINDOWS_TOTAL); //--- Leave, if the number of subwindows and the number of indicators have not changed if(subwindows_total==m_subwindows_total) return; //--- Store the current number of subwindows m_subwindows_total=subwindows_total; //--- For checking if expert subwindow is present bool is_subwindow=false; //--- find the expert subwindow for(int sw=0; sw<subwindows_total; sw++) { //--- Stop the cycle, if the expert subwindow has been found if(is_subwindow) break; //--- The number of indicators in this window/subwindow int indicators_total=::ChartIndicatorsTotal(m_chart_id,sw); //--- Iterate over all indicators in the window for(int i=0; i<indicators_total; i++) { //--- Get the short name of the indicator string indicator_name=::ChartIndicatorName(m_chart_id,sw,i); //--- If this is not the expert subwindow, go to the next if(indicator_name!=m_subwindow_shortname) continue; //--- Mark that expert has a subwindow is_subwindow=true; //--- If the subwindow number has changed, then // it is necessary to store the new number in all controls of the main form if(sw!=m_subwin) { //--- Store the subwindow number m_subwin=sw; //--- Store it in all the controls of the main form of the interface int elements_total=CWndContainer::ElementsTotal(0); for(int e=0; e<elements_total; e++) m_wnd[0].m_elements[e].SubwindowNumber(m_subwin); } u//--- break; } } //--- If the expert subwindow was not found, remove the expert if(!is_subwindow) { ::Print(__FUNCTION__," > Deleting expert subwindow causes the expert to be removed!"); //--- Removing the EA from the chart ::ExpertRemove(); } }
2. The previous version of the library has introduced the ability to toggle automatic change of the form width. Let us add a similar feature for height. The ON_WINDOW_CHANGE_SIZE identifier for the event of changing the form size is not suitable for solving this task, as changing the width and height will be handled as separate events. Therefore, the Defines.mqh file will have two separate identifiers instead of the ON_WINDOW_CHANGE_SIZE, as shown in the code listing below. The corresponding replacement of the identifier has been performed in other library files.
#define ON_WINDOW_CHANGE_XSIZE (3) // Change in the window size along the X axis #define ON_WINDOW_CHANGE_YSIZE (4) // Change in the window size along the Y axis
The CWindow::ChangeWindowHeight() method has been added for changing the form height. When calling the method after changing the form sizes, the message about the performed action is generated at the very end.
//+------------------------------------------------------------------+ //| Class for creating a form for controls | //+------------------------------------------------------------------+ class CWindow : public CElement { public: //--- Managing the size void ChangeWindowHeight(const int height); }; //+------------------------------------------------------------------+ //| Changes the height of the window | //+------------------------------------------------------------------+ void CWindow::ChangeWindowHeight(const int height) { //--- If the height has not changed, leave if(height==m_bg.YSize()) return; //--- Leave, if the window is minimized if(m_is_minimized) return; //--- Update the height for background CElement::YSize(height); m_bg.YSize(height); m_bg.Y_Size(height); m_bg_full_height=height; //--- A message that the window sizes have been changed ::EventChartCustom(m_chart_id,ON_WINDOW_CHANGE_YSIZE,(long)CElement::Id(),0,""); }
For the form height to change automatically, user developing an MQL application should set the corresponding mode using the CElement::AutoYResizeMode() method:
//+------------------------------------------------------------------+ //| Base control class | //+------------------------------------------------------------------+ class CElement { protected: //--- Mode of automatic control resizing bool m_auto_yresize_mode; u//--- public: //--- (1) Mode of automatic control height changing bool AutoYResizeMode(void) const { return(m_auto_yresize_mode); } void AutoYResizeMode(const bool flag) { m_auto_yresize_mode=flag; } };
Now, if the form autoresizing modes are enabled, then if the chart window size changes, the event handler of form adjusts the form sizes based on the CHARTEVENT_CHART_CHANGE event. This will work both in the indicator subwindow and in the chart window. It is possible to enable any one of the modes, or both at the same time.
//--- The chart properties change event if(id==CHARTEVENT_CHART_CHANGE) { //--- If the button is unpressed if(m_clamping_area_mouse==NOT_PRESSED) { //--- Get the chart window size SetWindowProperties(); //--- Adjustment of coordinates UpdateWindowXY(m_x,m_y); } //--- Change the width if the mode is enabled if(CElement::AutoXResizeMode()) ChangeWindowWidth(m_chart.WidthInPixels()-2); //--- Change the height if the mode is enabled if(CElement::AutoYResizeMode()) ChangeWindowHeight(m_chart.HeightInPixels(m_subwin)-3); u//--- return; }
3. The mode for automatic height changing also applies to all controls of the library. But in the current version, this will work only in controls listed below:
- CTabs – simple tabs.
- CIconTabs – tabs with icons.
- CCanvasTable – rendered table.
- CLineGraph – linear chart.
Similar to the earlier addition of the CElement::ChangeWidthByRightWindowSide() virtual method for changing the width of the control to the CElement class, let us add the corresponding method for automatic height changing. In addition, let us create the methods for setting the offset from the lower edge of the form, similar the previously added offsets from the right edge of the form when changing its width.
class CElement { protected: //--- Offset from the right/bottom edge of the form in the mode of automatic control width/height changing int m_auto_xresize_right_offset; int m_auto_yresize_bottom_offset; u//--- public: //--- Getting/setting the offset from the bottom edge of the form int AutoYResizeBottomOffset(void) const { return(m_auto_yresize_bottom_offset); } void AutoYResizeBottomOffset(const int offset) { m_auto_yresize_bottom_offset=offset; } u//--- public: //--- Change the height at the bottom edge of the window virtual void ChangeHeightByBottomWindowSide(void) {} };
Every control will have its own implementation of the ChangeWidthByRightWindowSide() method, which considers the unique features and modes. As an example, the listing below shows the code of this method in the CCanvasTable class:
//+------------------------------------------------------------------+ //| Change the height at the bottom edge of the window | //+------------------------------------------------------------------+ void CCanvasTable::ChangeHeightByBottomWindowSide(void) { //--- Leave, if the anchoring mode to the bottom of the form is enabled if(m_anchor_bottom_window_side) return; //--- Coordinates int y=0; //--- Size int x_size=(m_auto_xresize_mode)? m_wnd.X2()-m_area.X()-m_auto_xresize_right_offset : m_x_size; int y_size=m_wnd.Y2()-m_area.Y()-m_auto_yresize_bottom_offset; //--- Leave, if the size is less than specified if(y_size<60) return; //--- Set the new size of the table background ChangeMainSize(x_size,y_size); //--- Calculate the table sizes CalculateTableSize(); //--- Check for presence of a scrollbar bool is_scrollh=!(m_table_visible_x_size>=m_table_x_size); bool is_scrollv=!(m_table_visible_y_size>=m_table_y_size); //--- Offset relative to the presence of the scrollbar int offset=(is_scrollh || (!is_scrollh && !is_scrollv))? 0 : 2; //--- Calculate and set the new coordinate for the horizontal scrollbar y=m_area.Y2()-m_scrollh.ScrollWidth(); m_scrollh.YDistance(y); //--- Initialize the horizontal scrollbar for the new size m_scrollh.Reinit(m_table_x_size,m_table_visible_x_size); //--- Calculate and change the height of the vertical scrollbar m_scrollv.Reinit(m_table_y_size,m_table_visible_y_size-offset); m_scrollv.ChangeYSize((is_scrollh)? m_table_visible_y_size+2 : m_table_visible_y_size); //--- If the vertical scrollbar is not required if(!is_scrollv) { //--- Hide the vertical scrollbar m_scrollv.Hide(); //--- Change the width of the horizontal scrollbar m_scrollh.ChangeXSize(m_area.XSize()); } else { //--- Show the vertical scrollbar if(CElement::IsVisible()) m_scrollv.Show(); //--- Calculate and change the width of the horizontal scrollbar m_scrollh.ChangeXSize(m_area.XSize()-m_scrollv.ScrollWidth()+1); } //--- Manage the visibility of the horizontal scrollbar if(CElement::IsVisible()) { if(!is_scrollh) m_scrollh.Hide(); else m_scrollh.Show(); } //--- Resize the table ChangeTableSize(m_table_x_size,m_table_y_size,m_table_visible_x_size,m_table_visible_y_size-offset); //--- Draw the table DrawTable(); //--- Update the position of objects Moving(m_wnd.X(),m_wnd.Y()); }
For all this to work, certain additions and changes should be made to the CWndEvents class. Since the resizing events (width and height) are now generated with unique identifiers, two separate methods are required for their handling.
//+------------------------------------------------------------------+ //| Class for event handling | //+------------------------------------------------------------------+ class CWndEvents : public CWndContainer { private: //--- Handle changing the window sizes bool OnWindowChangeXSize(void); bool OnWindowChangeYSize(void); }; //+------------------------------------------------------------------+ //| ON_WINDOW_CHANGE_XSIZE event | //+------------------------------------------------------------------+ bool CWndEvents::OnWindowChangeXSize(void) { //--- If the signal is to "Resize the controls" if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_CHANGE_XSIZE) return(false); //--- Index of the active window int awi=m_active_window_index; //--- If the window identifiers match if(m_lparam!=m_windows[awi].Id()) return(true); //--- Change the width of all controls except the form int elements_total=CWndContainer::ElementsTotal(awi); for(int e=0; e<elements_total; e++) { //--- If it is a window, go to the next if(m_wnd[awi].m_elements[e].ClassName()=="CWindow") continue; //--- If the mode is enabled, adjust the width if(m_wnd[awi].m_elements[e].AutoXResizeMode()) m_wnd[awi].m_elements[e].ChangeWidthByRightWindowSide(); } u//--- return(true); } //+------------------------------------------------------------------+ //| ON_WINDOW_CHANGE_YSIZE event | //+------------------------------------------------------------------+ bool CWndEvents::OnWindowChangeYSize(void) { //--- If the signal is to "Resize the controls" if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_CHANGE_YSIZE) return(false); //--- Index of the active window int awi=m_active_window_index; //--- If the window identifiers match if(m_lparam!=m_windows[awi].Id()) return(true); //--- Change the width of all controls except the form int elements_total=CWndContainer::ElementsTotal(awi); for(int e=0; e<elements_total; e++) { //--- If it is a window, go to the next if(m_wnd[awi].m_elements[e].ClassName()=="CWindow") continue; //--- If the mode is enabled, adjust the height if(m_wnd[awi].m_elements[e].AutoYResizeMode()) m_wnd[awi].m_elements[e].ChangeHeightByBottomWindowSide(); } u//--- return(true); }
The CWndEvents::OnWindowChangeXSize() and CWndEvents::OnWindowChangeYSize() methods are called in the common CWndEvents::ChartEventCustom() method for handling custom events. The code listing below shows the shortened version of this method.
//+------------------------------------------------------------------+ //| CHARTEVENT_CUSTOM event | //+------------------------------------------------------------------+ void CWndEvents::ChartEventCustom(void) { //--- If the signal is to minimize the form //--- If the signal is to maximize the form //--- If the signal is to resize the controls along the X axis if(OnWindowChangeXSize()) return; //--- If the signal is to resize the controls along the Y axis if(OnWindowChangeYSize()) return; //--- If the signal is to hide the context menus below the initiating menu item //--- If the signal is to hide all context menus //--- If the signal is to open a dialog window //--- If the signal is to close a dialog window //--- If the signal is to zero the colors of all elements on the specified form //--- If the signal is to reset the priorities of the left mouse button click //--- If the signal is to restore the priorities of the left mouse button click }
Now, when resizing the chart window, if the mode of resizing the form and controls specified in it is enabled, they will also be automatically resized.
The screenshots below show an example of a graphical interface in an MQL application, that was created in the main chart window. For the application window (CWindow), the modes for automatic width and height adjustment to the chart window are set. For the «Main menu» (CMenuBar) and «Status bar» (CStatusBar) controls, the mode of automatic width and height adjustment to the MQL application window is set. For the «Tabs» (CTabs) and «Rendered table» (CCanvasTable) controls, the mode of automatic width and height adjustment to form size is set and the offsets from the bottom edge of the MQL application are specified.
Fig. 1. Minimum size of the terminal window. Graphical interface of an MQL application with automatic resizing modes enabled.
If the terminal window size changes, when the aforementioned modes are enabled, the graphical interface of the MQL application will also be resized accordingly.
Fig. 2. When the terminal window sizes change, the graphical interface of the MQL application will also ne resized.
4. Often, when designing the graphical interface with variable form sizes, it may be necessary for the controls to be positioned at the right or bottom part of the application window. Previously, there was an option to simply specify the coordinates for the control relative to the top left point of the form it was anchored to. Now, there are four options for positioning the control relative to the form:
- Top left.
- Top right.
- Bottom right.
- Bottom left.
In order to implement this idea, let us add two methods for setting/getting the modes of control positioning in the right and bottom of the form to the base class of controls (CElement):
class CElement { protected: //--- Anchor points of the control in the right and bottom of the window bool m_anchor_right_window_side; bool m_anchor_bottom_window_side; u//--- public: //--- Mode (getting/setting) of control anchor point to the (1) right and (2) bottom edge of the window bool AnchorRightWindowSide(void) const { return(m_anchor_right_window_side); } void AnchorRightWindowSide(const bool flag) { m_anchor_right_window_side=flag; } bool AnchorBottomWindowSide(void) const { return(m_anchor_bottom_window_side); } void AnchorBottomWindowSide(const bool flag) { m_anchor_bottom_window_side=flag; } };
For everything to work according to these modes, changes have been made to all classes of the library controls. As an example, here is the code of the method for creating a text label for the «Checkbox» (CCheckBox) control. Pay attention to the lines that calculate the coordinates and offsets for the graphical object.
//+------------------------------------------------------------------+ //| Creates the text label of the checkbox | //+------------------------------------------------------------------+ bool CCheckBox::CreateLabel(void) { //--- Forming the object name string name=CElement::ProgramName()+"_checkbox_lable_"+(string)CElement::Id(); //--- Coordinates int x =(m_anchor_right_window_side)? m_x-m_label_x_gap : m_x+m_label_x_gap; int y =(m_anchor_bottom_window_side)? m_y-m_label_y_gap : m_y+m_label_y_gap; //--- Text color according to the state color label_color=(m_check_button_state) ? m_label_color : m_label_color_off; //--- Set the object if(!m_label.Create(m_chart_id,name,m_subwin,x,y)) return(false); //--- set properties m_label.Description(m_label_text); m_label.Font(FONT); m_label.FontSize(FONT_SIZE); m_label.Color(label_color); m_label.Corner(m_corner); m_label.Anchor(m_anchor); m_label.Selectable(false); m_label.Z_Order(m_zorder); m_label.Tooltip("\n"); //--- Margins from the edge m_label.XGap((m_anchor_right_window_side)? x : x-m_wnd.X()); m_label.YGap((m_anchor_bottom_window_side)? y : y-m_wnd.Y()); //--- Initializing gradient array CElement::InitColorArray(label_color,m_label_color_hover,m_label_color_array); //--- Store the object pointer CElement::AddToArray(m_label); return(true); }
The Moving() methods of all controls in the library now consider the control positioning modes. As an example, the listing below shows the code of the CCheckBox::Moving() method:
//+------------------------------------------------------------------+ //| Moving controls | //+------------------------------------------------------------------+ void CCheckBox::Moving(const int x,const int y) { //--- Leave, if the element is hidden if(!CElement::IsVisible()) return; //--- If the anchored to the right if(m_anchor_right_window_side) { //--- Storing coordinates in the element fields CElement::X(m_wnd.X2()-XGap()); //--- Storing coordinates in the fields of the objects m_area.X(m_wnd.X2()-m_area.XGap()); m_check.X(m_wnd.X2()-m_check.XGap()); m_label.X(m_wnd.X2()-m_label.XGap()); } else { //--- Storing coordinates in the fields of the objects CElement::X(x+XGap()); //--- Storing coordinates in the fields of the objects m_area.X(x+m_area.XGap()); m_check.X(x+m_check.XGap()); m_label.X(x+m_label.XGap()); } //--- If the anchored to the bottom if(m_anchor_bottom_window_side) { //--- Storing coordinates in the element fields CElement::Y(m_wnd.Y2()-YGap()); //--- Storing coordinates in the fields of the objects m_area.Y(m_wnd.Y2()-m_area.YGap()); m_check.Y(m_wnd.Y2()-m_check.YGap()); m_label.Y(m_wnd.Y2()-m_label.YGap()); } else { //--- Storing coordinates in the fields of the objects CElement::Y(y+YGap()); //--- Storing coordinates in the fields of the objects m_area.Y(y+m_area.YGap()); m_check.Y(y+m_check.YGap()); m_label.Y(y+m_label.YGap()); } //--- Updating coordinates of graphical objects m_area.X_Distance(m_area.X()); m_area.Y_Distance(m_area.Y()); m_check.X_Distance(m_check.X()); m_check.Y_Distance(m_check.Y()); m_label.X_Distance(m_label.X()); m_label.Y_Distance(m_label.Y()); }
For clarity, the figure below schematically shows all possible combinations of the positioning modes and automatic resizing of controls. It is an abstract example, in which the form (see the ninth column «Result») is represented by a white rectangle with a black bold frame and size of 400 x 400 pixels, and as the control — gray rectangle with a size of 200 x 200 pixels. The relative coordinates and size of the control are also displayed for each combination. Dashes indicate that in those cases setting the size is not obligatory (if automatic resizing mode is enabled).
Fig. 3. Table listing different options in combination with the control positioning and automatic resizing.
5. Added the ON_CLICK_TAB event identifier for generating the event of switching tabs in the classes CTabs and CIconTabs.
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ ... #define ON_CLICK_TAB (27) // Switching tabs
Event with the ON_CLICK_TAB identifier can now be traced in the handler of a custom class, which provides even more capabilities for managing the appearance of the graphical interface.
6. Forced update of the coordinates has been added to the Show() methods of all controls. As an example, the listing below shows this method of the CSimpleButton class (line highlighted in yellow):
//+------------------------------------------------------------------+ //| Shows the button | //+------------------------------------------------------------------+ void CSimpleButton::Show(void) { //--- Leave, if the element is already visible if(CElement::IsVisible()) return; //--- Make all objects visible m_button.Timeframes(OBJ_ALL_PERIODS); //--- State of visibility CElement::IsVisible(true); //--- Update the position of objects Moving(m_wnd.X(),m_wnd.Y()); }
In certain cases, active use of the MQL application graphical interface could lead to some of its controls to be positioned incorrectly. Now this issue is resolved.
7. The methods for getting the pointers to buttons have been added to the CWindow class:
//+------------------------------------------------------------------+ //| Class for creating a form for controls | //+------------------------------------------------------------------+ class CWindow : public CElement { public: //--- Returns pointers to form buttons CBmpLabel *GetCloseButtonPointer(void) { return(::GetPointer(m_button_close)); } CBmpLabel *GetRollUpButtonPointer(void) { return(::GetPointer(m_button_unroll)); } CBmpLabel *GetUnrollButtonPointer(void) { return(::GetPointer(m_button_rollup)); } CBmpLabel *GetTooltipButtonPointer(void) { return(::GetPointer(m_button_tooltip)); } };
Thus, the library users now have the ability to change the properties of these graphical objects at any moment of the MQL application's life cycle after the graphical interface had been created. For example, if the button tooltips previously were predefined default values, now then can be set independently. This may be useful when creating multilingual MQL applications.
8. Fixes in the CTable class. Added a check to the main method of creating a control (see the code below). The visible part does not have to be common anymore. Now, if user makes a mistake when setting the table properties, the values will be fixed automatically.
//+------------------------------------------------------------------+ //| Create edit box table | //+------------------------------------------------------------------+ bool CTable::CreateTable(const long chart_id,const int subwin,const int x,const int y) { //--- Leave, if there is no form pointer if(!CElement::CheckWindowPointer(::CheckPointer(m_wnd))) return(false); //--- The visible part does not have to be common anymore m_visible_rows_total =(m_visible_rows_total>m_rows_total)? m_rows_total : m_visible_rows_total; m_visible_columns_total =(m_visible_columns_total>m_columns_total)? m_columns_total : m_visible_columns_total; //--- Initializing variables m_id =m_wnd.LastId()+1; m_chart_id =chart_id; m_subwin =subwin; m_x =x; m_y =y; m_x_size =(m_x_size<1 || m_auto_xresize_mode)? (m_anchor_right_window_side)? m_wnd.X2()-m_x+m_x_size-(m_wnd.X2()-m_x)+1-m_auto_xresize_right_offset : m_wnd.X2()-m_x-m_auto_xresize_right_offset : m_x_size; m_y_size =m_row_y_size*m_visible_rows_total-(m_visible_rows_total-1)+2; //--- Margins from the edge CElement::XGap((m_anchor_right_window_side)? m_x : m_x-m_wnd.X()); CElement::YGap((m_anchor_bottom_window_side)? m_y : m_y-m_wnd.Y()); //--- Create the table if(!CreateArea()) return(false); if(!CreateCells()) return(false); if(!CreateScrollV()) return(false); if(!CreateScrollH()) return(false); //--- Hide the element if the window is a dialog one or is minimized if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized()) Hide(); u//--- return(true); }
Application for Testing the Updates
For the test, let us slightly change the MQL application from the previous article to make it possible to demonstrate everything that has been presented in this article. Create the EA in subwindow of the “SubWindow” indicator. Main window size of the graphical interface will automatically adjust to the subwindow size. Subwindow height will be manually changeable. To do this, 'false' should be passed (highlighted in green in the code below) when calling the CWindow::RollUpSubwindowMode() method.
The code listing below also demonstrates how to gain access to managing the properties of the buttons in the main window of the graphical interface in the application. In this case, the example shows setting the tooltips.
//+------------------------------------------------------------------+ //| Creates a form for controls | //+------------------------------------------------------------------+ bool CProgram::CreateWindow(const string caption_text) { //--- Add the window pointer to the window array CWndContainer::AddWindow(m_window); //--- Coordinates int x=1; int y=1; //--- Properties m_window.Movable(false); m_window.UseRollButton(); m_window.AutoXResizeMode(true); m_window.AutoYResizeMode(true); m_window.RollUpSubwindowMode(false,false); //--- Creating the form if(!m_window.CreateWindow(m_chart_id,m_subwin,caption_text,x,y)) return(false); //--- Set the tooltips m_window.GetCloseButtonPointer().Tooltip("Close program"); m_window.GetUnrollButtonPointer().Tooltip("Unroll"); m_window.GetRollUpButtonPointer().Tooltip("Roll up"); return(true); }
On the first tab, all controls will be anchored to the right side of the form (see the screenshot below). If the form width is changed, they will remain at the same distance from its right edge.
Fig. 4. Controls of the first tab are anchored to the right side of the form.
The listing below shows an example of code for creating the "Simple button" (CSimpleButton) control. In order to anchor the control to the right side, it is sufficient to call the AnchorRightWindowSide() method, and pass it the value true.
//+------------------------------------------------------------------+ //| Creates simple button 1 | //+------------------------------------------------------------------+ bool CProgram::CreateSimpleButton1(const int x_gap,const int y_gap,string button_text) { //--- Store the window pointer m_simple_button1.WindowPointer(m_window); //--- Attach to the first tab m_tabs.AddToElementsArray(0,m_simple_button1); //--- Coordinates int x=m_window.X()+x_gap; int y=m_window.Y()+y_gap; //--- Set properties before creation m_simple_button1.ButtonXSize(140); m_simple_button1.BackColor(C'255,140,140'); m_simple_button1.BackColorHover(C'255,180,180'); m_simple_button1.BackColorPressed(C'255,120,120'); m_simple_button1.AnchorRightWindowSide(true); //--- Creating a button if(!m_simple_button1.CreateSimpleButton(m_chart_id,m_subwin,button_text,x,y)) return(false); //--- Add the element pointer to the base CWndContainer::AddToElementsArray(0,m_simple_button1); return(true); }
The second tab will be assigned only a rendered table (CCanvasTable), which will adjust to the form sizes when the subwindow width and height changes.
Fig. 5. Rendered table that adjusts to the form sizes.
For everything to work as intended, it is necessary to use the AutoXResizeMode() and AutoYResizeMode() methods and enable the modes for automatic horizontal and vertical resizing. With the AutoXResizeRightOffset() and AutoYResizeBottomOffset() methods, it is possible to adjust the offsets from the right and bottom borders of the control to the right and bottom edges of the form. In this case, the offset from the right edge is set to 1 pixel, and 25 pixels from the bottom (see the code below).
//+------------------------------------------------------------------+ //| Create a rendered table | //+------------------------------------------------------------------+ bool CProgram::CreateCanvasTable(const int x_gap,const int y_gap) { #define COLUMNS3_TOTAL 15 #define ROWS3_TOTAL 30 //--- Store pointer to the form m_canvas_table.WindowPointer(m_window); //--- Attach to the second tab m_tabs.AddToElementsArray(1,m_canvas_table); //--- Coordinates int x=m_window.X()+x_gap; int y=m_window.Y()+y_gap; //--- Array of column widths int width[COLUMNS3_TOTAL]; ::ArrayInitialize(width,70); width[0]=100; width[1]=90; //--- Array of text alignment in columns ENUM_ALIGN_MODE align[COLUMNS3_TOTAL]; ::ArrayInitialize(align,ALIGN_CENTER); align[0]=ALIGN_RIGHT; align[1]=ALIGN_RIGHT; align[2]=ALIGN_LEFT; //--- Set properties before creation m_canvas_table.XSize(400); m_canvas_table.YSize(200); m_canvas_table.TableSize(COLUMNS3_TOTAL,ROWS3_TOTAL); m_canvas_table.TextAlign(align); m_canvas_table.ColumnsWidth(width); m_canvas_table.GridColor(clrLightGray); m_canvas_table.AutoXResizeMode(true); m_canvas_table.AutoYResizeMode(true); m_canvas_table.AutoXResizeRightOffset(1); m_canvas_table.AutoYResizeBottomOffset(25); //--- Populate the table with data for(int c=0; c<COLUMNS3_TOTAL; c++) for(int r=0; r<ROWS3_TOTAL; r++) m_canvas_table.SetValue(c,r,string(c)+":"+string(r)); //--- Create control if(!m_canvas_table.CreateTable(m_chart_id,m_subwin,x,y)) return(false); //--- Add the object to the common array of object groups CWndContainer::AddToElementsArray(0,m_canvas_table); return(true); }
A line graph (CLineGraph) will be placed on the third tab, also with automatic adjustment to the form sizes:
Fig. 6. The «Line graph» control that adjusts to the form sizes.
The fourth and fifth tabs demonstrate the anchoring to right edge of many other library controls:
Fig. 7. Controls on the fourth tab.
Fig. 8. Controls on the fifth tab.
The test application featured in the article can be downloaded using the below link for further studying.
Conclusion
The library for creating graphical interfaces at the current stage of development looks like in the schematic below.
Fig. 9. Structure of the library at the current stage of development.
In the next version, the library will be expanded with additional controls, which may be necessary in development of MQL applications. The existing controls will be improved and supplemented with new features.
Below you can download the third version of the Easy And Fast library. If you have questions on using material from those files, you can refer to the detailed description of this article series or ask your question in the comments of this article.
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/2723
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
I have downloaded the Zip and unpacked to a fresh MT5.
I don´t know what happened, but it seems there is a major problem with the unpacked ZIP !
I had to remove about 700 unwanted Chars in different Files and Folders (pls refer Screenshot)
Would be nice, if someone could reup fixed Files, that not everybody who downloads this has to use a Replace program to solve it by himself :)
However, for now I have also attached the fixed ZIP and also fixed the "double Quotes" Issue from "Menuitem.mqh" around line 290
...