Graphical Interfaces XI: Rendered controls (build 14.2)
Contents
- Introduction
- Methods for drawing controls
- New design of the graphical interface
- Tooltips
- New event identifiers
- Optimizing the library core
- Application for testing the controls
- Conclusion
Introduction
The first article Graphical Interfaces I: Preparation of the Library Structure (Chapter 1) considers in detail what this library is for. The full version of the library at the current stage of development is available at the end of each article in the series. Files must be placed under the same directories as they are located in the archive.
In the updated version of the library, all its controls will be drawn on separate graphical objects of the OBJ_BITMAP_LABEL type. In addition, we will continue to describe the global optimization of the library code. This description has been started in the previous article. Now let us consider the changes in the core classes of the library. The new version of the library has become even more object-oriented. The code has become even more comprehensible. This helps users to develop the library independently according to their own tasks.
Methods for drawing controls
An instance of the canvas class has been declared in the CElement class. Its methods allow creating an object for drawing and deleting it. If necessary, its pointer can be obtained.
class CElement : public CElementBase { protected: //--- Canvas for drawing a control CRectCanvas m_canvas; //--- public: //--- Returns the pointer to the control's canvas CRectCanvas *CanvasPointer(void) { return(::GetPointer(m_canvas)); } };
Now there is a general method for creating an object (canvas) for drawing the appearance of a control. It is located in the CElement base class and can be accessed from all classes of the library controls. The CElement::CreateCanvas() method is used for creating a graphical object of this type. As arguments, it must be passed the (1) name, (2) coordinates, (3) dimensions and (4) color format. The default format is COLOR_FORMAT_ARGB_NORMALIZE, which allows making the controls transparent. In case invalid dimensions are passed, they will be corrected in the beginning of the method. Once the object is created and attached to a chart with the MQL application, the base properties are set, which were previously repeated in all classes of controls.
class CElement : public CElementBase { public: //--- Creating a canvas bool CreateCanvas(const string name,const int x,const int y, const int x_size,const int y_size,ENUM_COLOR_FORMAT clr_format=COLOR_FORMAT_ARGB_NORMALIZE); }; //+------------------------------------------------------------------+ //| Creating a canvas for drawing a control | //+------------------------------------------------------------------+ bool CElement::CreateCanvas(const string name,const int x,const int y, const int x_size,const int y_size,ENUM_COLOR_FORMAT clr_format=COLOR_FORMAT_ARGB_NORMALIZE) { //--- Adjust the sizes int xsize =(x_size<1)? 50 : x_size; int ysize =(y_size<1)? 20 : y_size; //--- Reset the last error ::ResetLastError(); //--- Creating an object if(!m_canvas.CreateBitmapLabel(m_chart_id,m_subwin,name,x,y,xsize,ysize,clr_format)) { ::Print(__FUNCTION__," > Failed to create a canvas for drawing the control ("+m_class_name+"): ",::GetLastError()); return(false); } //--- Reset the last error ::ResetLastError(); //--- Get the pointer to the base class CChartObject *chart=::GetPointer(m_canvas); //--- Attach to the chart if(!chart.Attach(m_chart_id,name,(int)m_subwin,(int)1)) { ::Print(__FUNCTION__," > Failed to attach the canvas for drawing to the chart: ",::GetLastError()); return(false); } //--- Properties m_canvas.Tooltip("\n"); m_canvas.Corner(m_corner); m_canvas.Selectable(false); //--- All controls, except the form, have a higher priority than the main control Z_Order((dynamic_cast<CWindow*>(&this)!=NULL)? 0 : m_main.Z_Order()+1); //--- Coordinates m_canvas.X(x); m_canvas.Y(y); //--- Size m_canvas.XSize(x_size); m_canvas.YSize(y_size); //--- Offsets from the extreme point m_canvas.XGap(CalculateXGap(x)); m_canvas.YGap(CalculateYGap(y)); return(true); }
Let us move on to the base methods for drawing the controls. They are all located in the CElement class and declared as virtual.
First of all comes drawing the background. In the basic version, it is a simple color fill, which uses the CElement::DrawBackground() method. If necessary, transparency can be enabled. To do this, use the CElement::Alpha() method, with the alpha channel value from 0 to 255 passed as the argument. A zero value means full transparency. In the current version, transparency applies only to the background fill and the border. Text and images will remain fully opaque and clear at any value of the alpha channel.
class CElement : public CElementBase { protected: //--- Value of the alpha channel (transparency of the control) uchar m_alpha; //--- public: //--- Value of the alpha channel (transparency of the control) void Alpha(const uchar value) { m_alpha=value; } uchar Alpha(void) const { return(m_alpha); } //--- protected: //--- Draws the background virtual void DrawBackground(void); }; //+------------------------------------------------------------------+ //| Draws the background | //+------------------------------------------------------------------+ void CElement::DrawBackground(void) { m_canvas.Erase(::ColorToARGB(m_back_color,m_alpha)); }
It is often necessary to draw a border for a particular control. The CElement::DrawBorder() method draws a border around the edges of the canvas object. The Rectangle() method can also be used for this purpose. It draws a rectangle without fill.
class CElement : public CElementBase { protected: //--- Draws the frame virtual void DrawBorder(void); }; //+------------------------------------------------------------------+ //| Draws the border | //+------------------------------------------------------------------+ void CElement::DrawBorder(void) { //--- Coordinates int x1=0,y1=0; int x2=m_canvas.X_Size()-1; int y2=m_canvas.Y_Size()-1; //--- Draw a rectangle without fill m_canvas.Rectangle(x1,y1,x2,y2,::ColorToARGB(m_border_color,m_alpha)); }
The previous article has already mentioned that any number of image groups can be assigned to any control. Therefore, the method for drawing a control must be able to output all images set by user. The CElement::DrawImage() method is used for this purpose here. The program sequentially iterates over all groups and images in them, outputting them to the canvas pixel by pixel. Before the cycle for outputting the image starts, the currently selected image in the group is determined. See the code of this method:
class CElement : public CElementBase { protected: //--- Draws the image virtual void DrawImage(void); }; //+------------------------------------------------------------------+ //| Draws the image | //+------------------------------------------------------------------+ void CElement::DrawImage(void) { //--- The number of groups uint group_total=ImagesGroupTotal(); //--- Draw the image for(uint g=0; g<group_total; g++) { //--- Index of the selected image int i=SelectedImage(g); //--- If there are no images if(i==WRONG_VALUE) continue; //--- Coordinates int x =m_images_group[g].m_x_gap; int y =m_images_group[g].m_y_gap; //--- Size uint height =m_images_group[g].m_image[i].Height(); uint width =m_images_group[g].m_image[i].Width(); //--- Draw for(uint ly=0,p=0; ly<height; ly++) { for(uint lx=0; lx<width; lx++,p++) { //--- If there is no color, go to the next pixel if(m_images_group[g].m_image[i].Data(p)<1) continue; //--- Get the color of the lower layer (cell background) and color of the specified pixel of the icon uint background =::ColorToARGB(m_canvas.PixelGet(x+lx,y+ly)); uint pixel_color =m_images_group[g].m_image[i].Data(p); //--- Blend the colors uint foreground=::ColorToARGB(m_clr.BlendColors(background,pixel_color)); //--- Draw the pixel of the overlay icon m_canvas.PixelSet(x+lx,y+ly,foreground); } } } }
Many controls have a text description. It can be displayed using the CElement::DrawText() method. Several fields in this method allow customizing the display of text depending on the state of the control. Three states of the control are available:
- locked;
- pressed;
- focused (hovered by mouse).
In addition, the method considers if the mode of text alignment to the center is enabled. Here is its code:
class CElement : public CElementBase { protected: //--- Draw text virtual void DrawText(void); }; //+------------------------------------------------------------------+ //| Draws the text | //+------------------------------------------------------------------+ void CElement::DrawText(void) { //--- Coordinates int x =m_label_x_gap; int y =m_label_y_gap; //--- Define the color for the text label color clr=clrBlack; //--- If the control is locked if(m_is_locked) clr=m_label_color_locked; else { //--- If the control is pressed if(!m_is_pressed) clr=(m_mouse_focus)? m_label_color_hover : m_label_color; else { if(m_class_name=="CButton") clr=m_label_color_pressed; else clr=(m_mouse_focus)? m_label_color_hover : m_label_color_pressed; } } //--- Font properties m_canvas.FontSet(m_font,-m_font_size*10,FW_NORMAL); //--- Draw the text with consideration of the center alignment mode if(m_is_center_text) { x =m_x_size>>1; y =m_y_size>>1; m_canvas.TextOut(x,y,m_label_text,::ColorToARGB(clr),TA_CENTER|TA_VCENTER); } else m_canvas.TextOut(x,y,m_label_text,::ColorToARGB(clr),TA_LEFT); }
All of the above methods will be called in the common public virtual method CElement::Draw(). It does not have a base code, because the called set of methods for drawing will be unique in each control.
class CElement : public CElementBase { public: //--- Draws the control virtual void Draw(void) {} };
Consider the CElement::Update() method. It is called every time a program change is made to a control of the graphical interface. Two calling options are available: (1) completely redrawing the control or (2) applying the changes made before (see the code listing below). This method is also declared as virtual, since certain classes of controls may have their unique versions, which consider the peculiarities in the methods and sequence of rendering.
class CElement : public CElementBase { public: //--- Updates the control to display the latest changes virtual void Update(const bool redraw=false); }; //+------------------------------------------------------------------+ //| Updating the control | //+------------------------------------------------------------------+ void CElement::Update(const bool redraw=false) { //--- With redrawing the control if(redraw) { Draw(); m_canvas.Update(); return; } //--- Apply m_canvas.Update(); }
New design of the graphical interface
Since all controls of the library are now rendered, it became possible to implement a new design for the graphical interface. There is no need to invent anything special, a ready-made solution can be used. The laconic aesthetics of the Windows 10 OS was used as the basis.
Images for icons in such elements as form buttons for controls, radio buttons, checkboxes, comboboxes, menu items, tree list items and others have been made similar to Windows 10.
It was mentioned earlier that transparency can now be set to any control. The screenshot below shows an example of a translucent window (CWindow). The value of the alpha channel here is 200.
Fig. 8. Demonstration of the transparency of the form for controls.
To make the entire area of the form transparent, use the CWindow::TransparentOnlyCaption() method. The default mode applies transparency to header only.
class CWindow : public CElement { private: //--- Enables transparency only for the header bool m_transparent_only_caption; //--- public: //--- Enables transparency mode only for the header void TransparentOnlyCaption(const bool state) { m_transparent_only_caption=state; } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CWindow::CWindow(void) : m_transparent_only_caption(true) { ... }
Below is the appearance of the different types of buttons:
Fig. 9. Demonstration of the appearance of several button types.
The next screenshot shows the current appearance of the checkboxes, spin edit boxes, combobox with a drop-down list and a scrollbar, as well as the numeric sliders. Please note that it is now possible to make animated icons. The third item of the status bar imitates a disconnection from the server. Its appearance is an exact copy of a similar element in the status bar of MetaTrader 5.
Fig. 10. Demonstration of the appearance of checkboxes, combo boxes, sliders and other controls.
The appearance of other controls from the graphical interface library can be seen in the test MQL application attached to this article.
Tooltips
Additional methods for managing the display of the tooltips in controls have been added to the CElement class. It is now possible to set a standard tooltip for any control, if its text does not exceed 63 characters. Use the CElement::Tooltip() methods for setting and getting the text of the tooltip.
class CElement : public CElementBase { protected: //--- Tooltip text string m_tooltip_text; //--- public: //--- Tooltip void Tooltip(const string text) { m_tooltip_text=text; } string Tooltip(void) const { return(m_tooltip_text); } };
Use the CElement::ShowTooltip() method to enable or disable the tooltip display.
class CElement : public CElementBase { public: //--- Tooltip display mode void ShowTooltip(const bool state); }; //+------------------------------------------------------------------+ //| Setting the tooltip display | //+------------------------------------------------------------------+ void CElement::ShowTooltip(const bool state) { if(state) m_canvas.Tooltip(m_tooltip_text); else m_canvas.Tooltip("\n"); }
Each class of controls has methods for getting the pointers to the nested controls. For example, if it is necessary to create tooltips for the form buttons, then the following lines of code should be added to the form creation method in the custom class:
... //--- Set the tooltips m_window.GetCloseButtonPointer().Tooltip("Close"); m_window.GetCollapseButtonPointer().Tooltip("Collapse/Expand"); m_window.GetTooltipButtonPointer().Tooltip("Tooltips"); ...
See how it works in the figure below. The standard tooltips have been enabled for the form buttons. In controls that might need a description longer than 63 characters, use the CTooltip control.
Fig. 11. Demonstration of two types of tooltips (standard and custom).
New event identifiers
New event identifiers have been added. This significantly reduced the consumption of CPU resources. How has this been achieved?
When creating a large MQL application with a graphical interface and a lot of controls, it is important to minimize the CPU consumption.
If you the mouse over a control, it is highlighted. This indicates that the control is available for interaction. However, not all controls are available and visible at the same time.
- Drop-down lists and calendars, context menus are hidden most of the time. They are opened occasionally, only to let the user select the necessary option, date or mode.
- Groups of controls can be assigned to different tabs, but only one tab can be opened at a time.
- If the form is minimized, then all its controls are hidden as well.
- If a dialog box is open, then only this form will have event handling.
Logically, there is no point in constantly processing the entire list of controls in the graphical interface when only some of them are available for use. It is necessary to generate an array of event handling only for the list of opened controls.
There are also controls with interactions that only affect the controls themselves. Therefore, such a control is the only one that must be left available for processing. Let us list these controls and cases:
- Moving the scrollbar thumb (CScroll). It is necessary to enable processing only the scrollbar itself and the control it is part of (list view, table, multiline text box, etc.).
- Moving the slider thumb (CSlider). For this, it is sufficient for the slider control and spin edit box to be available, where the changes in values will be reflected.
- Changing the width of columns in the table (CTable). Only the table must be left available for processing.
- Changing the width of lists in the Tree view control (CTreeView). Only this control must be handled when dragging the adjacent border of lists.
- Moving the form (CWindow). All controls are excluded from handling, except the form while it is moved.
In all listed cases, the controls need to send messages, that must be received and processed in the library core. The core will process two event identifiers for determining the availability of controls (ON_SET_AVAILABLE) and generating an array of controls (ON_CHANGE_GUI). All event identifiers are located in the Define.mqh file:
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ ... #define ON_CHANGE_GUI (28) // Graphical interface has changed #define ON_SET_AVAILABLE (39) // Set available items ...
Hide and show the controls using the methods Show() and Hide(). A new property has been added to the CElement class for setting the availability. Its value is set using the virtual public method CElement::IsAvailable(). Here, similar to other methods that define the state of controls, the passed value is set to nested controls as well. The priorities of the left mouse button click are set relative to the passed state. If the control must be unavailable, the priorities will be reset.
class CElement : public CElementBase { protected: bool m_is_available; // availability //--- public: //--- Sign of available control virtual void IsAvailable(const bool state) { m_is_available=state; } bool IsAvailable(void) const { return(m_is_available); } }; //+------------------------------------------------------------------+ //| Control availability | //+------------------------------------------------------------------+ void CElement::IsAvailable(const bool state) { //--- Leave, if already set if(state==CElementBase::IsAvailable()) return; //--- Set CElementBase::IsAvailable(state); //--- Other controls int elements_total=ElementsTotal(); for(int i=0; i<elements_total; i++) m_elements[i].IsAvailable(state); //--- Set priorities of the left mouse button click if(state) SetZorders(); else ResetZorders(); }
As an example, here is the code of the CComboBox::ChangeComboBoxListState() method, which determines the visibility of the drop-down list in the combobox control.
If a combobox button is pressed and it is necessary to show the list view, then an event with the ON_SET_AVAILABLE identifier is sent immediately after the list view is displayed. As additional parameters, the (1) identifier of the control and (2) the flag of the required action for the event handler are passed: restore all visible controls or make available only the control that has the identifier specified in the event. A flag with the value 1 is used for restoring, while the value 0 sets the availability of the specified control.
The message with the ON_SET_AVAILABLE identifier is followed by a message with the ON_CHANGE_GUI event identifier. Handling it will involve generation of an array with the currently available controls.
//+------------------------------------------------------------------+ //| Changes the current state of the combobox for the opposite | //+------------------------------------------------------------------+ void CComboBox::ChangeComboBoxListState(void) { //--- If the button is pressed if(m_button.IsPressed()) { //--- Show the list view m_listview.Show(); //--- Send a message to determine the available controls ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),0,""); //--- Send a message about the change in the graphical interface ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,""); } else { //--- Hide the list view m_listview.Hide(); //--- Send a message to restore the controls ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),1,""); //--- Send a message about the change in the graphical interface ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,""); } }
But for the Tabs control, for instance, it is sufficient to send only one of the described events for processing, the one with the ON_CHANGE_GUI identifier. There is no need to make certain controls available. When switching tabs, the visibility state is set to controls, that are assigned to the tab group. In the CTabs class, the visibility of controls groups is managed by the CTabs::ShowTabElements() method, which has been modified in the new version of the library. It may sometimes be necessary to place a group of tabs inside a tab. Therefore, even if displaying the controls of the selected tab reveals that one of them has a type of CTabs, then the CTabs::ShowTabElements() method will immediately be called in this control as well. This approach allows for placement of tabs at any level of nesting.
//+------------------------------------------------------------------+ //| Show controls of the selected tab only | //+------------------------------------------------------------------+ void CTabs::ShowTabElements(void) { //--- Leave, if the tabs are hidden if(!CElementBase::IsVisible()) return; //--- Check index of the selected tab CheckTabIndex(); //--- uint tabs_total=TabsTotal(); for(uint i=0; i<tabs_total; i++) { //--- Get the number of controls attached to the tab int tab_elements_total=::ArraySize(m_tab[i].elements); //--- If this tab is selected if(i==m_selected_tab) { //--- Display the tab controls for(int j=0; j<tab_elements_total; j++) { //--- Display the controls CElement *el=m_tab[i].elements[j]; el.Reset(); //--- If this is the Tabs control, show the controls of the opened one CTabs *tb=dynamic_cast<CTabs*>(el); if(tb!=NULL) tb.ShowTabElements(); } } //--- Hide the controls of inactive tabs else { for(int j=0; j<tab_elements_total; j++) m_tab[i].elements[j].Hide(); } } //--- Send a message about it ::EventChartCustom(m_chart_id,ON_CLICK_TAB,CElementBase::Id(),m_selected_tab,""); }
Once the controls of the selected tab are displayed, the method sends a message stating that the graphical interface has changed and it is necessary to generate the array of controls available for processing.
//+------------------------------------------------------------------+ //| Pressing a tab in a group | //+------------------------------------------------------------------+ bool CTabs::OnClickTab(const int id,const int index) { //--- Leave, if (1) the identifiers do not match or (2) the control is locked if(id!=CElementBase::Id() || CElementBase::IsLocked()) return(false); //--- Leave, if the index does not match if(index!=m_tabs.SelectedButtonIndex()) return(true); //--- Store index of the selected tab SelectedTab(index); //--- Redraw the control Reset(); Update(true); //--- Show controls of the selected tab only ShowTabElements(); //--- Send a message about the change in the graphical interface ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0.0,""); return(true); }
Two new identifiers for generation of events have been added to the Defines.mqh file.
- ON_MOUSE_FOCUS — mouse cursor entered the area of the control;
- ON_MOUSE_BLUR — mouse cursor left the area of the control.
... #define ON_MOUSE_BLUR (34) // Mouse cursor left the area of the control #define ON_MOUSE_FOCUS (35) // Mouse cursor entered the area of the control ...
These events are generated only when the boundaries of controls are crossed. The base class of controls (CElementBase) contains the CElementBase::CheckCrossingBorder() method, which determines the moment the mouse cursor crosses the boundaries of the control areas. Let us supplement it with generation of the events described above:
//+------------------------------------------------------------------+ //| Checking the crossing of the control borders | //+------------------------------------------------------------------+ bool CElementBase::CheckCrossingBorder(void) { //--- If this is the moment of crossing the borders of the control if((MouseFocus() && !IsMouseFocus()) || (!MouseFocus() && IsMouseFocus())) { IsMouseFocus(MouseFocus()); //--- Message about the crossing into the control if(MouseFocus()) ::EventChartCustom(m_chart_id,ON_MOUSE_FOCUS,m_id,m_index,m_class_name); //--- Message about the crossing out of the control else ::EventChartCustom(m_chart_id,ON_MOUSE_BLUR,m_id,m_index,m_class_name); //--- return(true); } //--- return(false); }
In the current version of the library, these events are handled only in the main menu (CMenuBar). Let us see how it works.
Once the main menu is created and stored, its items (CMenuItem) fall into the storage list as separate controls. The CMenuItem class is derived from CButton (the Button control). Therefore, a call to the event handler of the menu item starts with a call to the event handler of the CButton base class.
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CMenuItem::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Handle the event in the base class CButton::OnEvent(id,lparam,dparam,sparam); ... }
The base event handler already contains tracking of the button crossing, it does not need to be duplicated in the CMenuItem derived class.
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CButton::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Handling the mouse move event if(id==CHARTEVENT_MOUSE_MOVE) { //--- Redraw the control if borders were crossed if(CheckCrossingBorder()) Update(true); //--- return; } ... }
If the cursor crosses the border inside the button area, an event with the ON_MOUSE_FOCUS identifier is generated. Now the event handler of the CMenuBar class uses this very event to switch the context menus, when the Main menu control is activated.
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CMenuBar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Handling the event of changing focus on the menu items if(id==CHARTEVENT_CUSTOM+ON_MOUSE_FOCUS) { //--- Leave, if (2) the main menu has not been activated or (2) identifiers do not match if(!m_menubar_state || lparam!=CElementBase::Id()) return; //--- Switch the context menu by the activated item of the main menu SwitchContextMenuByFocus(); return; } ... }
Optimizing the library core
Let us consider the changes, fixes and additions made to the CWndContainer and CWndEvents classes, which can be called as the core of the library. After all, they organize the access to all of its controls and handle the event flows that are generated by the controls of the graphical interface.
A template method CWndContainer::ResizeArray() has been added to the CWndContainer class for working with arrays. An array of any type passed to this method will be increased by one element, and the method will return the index of the last element.
//+------------------------------------------------------------------+ //| Class for storing all interface objects | //+------------------------------------------------------------------+ class CWndContainer { private: //--- Increases the array by one element and returns the last index template<typename T> int ResizeArray(T &array[]); }; //+------------------------------------------------------------------+ //| Increases the array by one element and returns the last index | //+------------------------------------------------------------------+ template<typename T> int CWndContainer::ResizeArray(T &array[]) { int size=::ArraySize(array); ::ArrayResize(array,size+1,RESERVE_SIZE_ARRAY); return(size); }
Let me remind you that private arrays have been declared in the WindowElements structure for many controls in the CWndContainer class (storage of pointers to all controls of the graphical interface). In order to obtain the number of controls of a certain type from this list, a universal CWndContainer::ElementsTotal() method has been implemented. Pass it the index of the window and the type of the control in order to obtain their number in the graphical interface of the MQL application. A new ENUM_ELEMENT_TYPE enumeration has been added to the Enums.mqh file for specifying the type of the control:
//+------------------------------------------------------------------+ //| Enumeration of the control types | //+------------------------------------------------------------------+ enum ENUM_ELEMENT_TYPE { E_CONTEXT_MENU =0, E_COMBO_BOX =1, E_SPLIT_BUTTON =2, E_MENU_BAR =3, E_MENU_ITEM =4, E_DROP_LIST =5, E_SCROLL =6, E_TABLE =7, E_TABS =8, E_SLIDER =9, E_CALENDAR =10, E_DROP_CALENDAR =11, E_SUB_CHART =12, E_PICTURES_SLIDER =13, E_TIME_EDIT =14, E_TEXT_BOX =15, E_TREE_VIEW =16, E_FILE_NAVIGATOR =17, E_TOOLTIP =18 };
The code of the CWndContainer::ElementsTotal() method is presented in the following listing:
//+------------------------------------------------------------------+ //| No of controls of specified type at the specified window index | //+------------------------------------------------------------------+ int CWndContainer::ElementsTotal(const int window_index,const ENUM_ELEMENT_TYPE type) { //--- Checking for exceeding the array range int index=CheckOutOfRange(window_index); if(index==WRONG_VALUE) return(WRONG_VALUE); //--- int elements_total=0; //--- switch(type) { case E_CONTEXT_MENU : elements_total=::ArraySize(m_wnd[index].m_context_menus); break; case E_COMBO_BOX : elements_total=::ArraySize(m_wnd[index].m_combo_boxes); break; case E_SPLIT_BUTTON : elements_total=::ArraySize(m_wnd[index].m_split_buttons); break; case E_MENU_BAR : elements_total=::ArraySize(m_wnd[index].m_menu_bars); break; case E_MENU_ITEM : elements_total=::ArraySize(m_wnd[index].m_menu_items); break; case E_DROP_LIST : elements_total=::ArraySize(m_wnd[index].m_drop_lists); break; case E_SCROLL : elements_total=::ArraySize(m_wnd[index].m_scrolls); break; case E_TABLE : elements_total=::ArraySize(m_wnd[index].m_tables); break; case E_TABS : elements_total=::ArraySize(m_wnd[index].m_tabs); break; case E_SLIDER : elements_total=::ArraySize(m_wnd[index].m_sliders); break; case E_CALENDAR : elements_total=::ArraySize(m_wnd[index].m_calendars); break; case E_DROP_CALENDAR : elements_total=::ArraySize(m_wnd[index].m_drop_calendars); break; case E_SUB_CHART : elements_total=::ArraySize(m_wnd[index].m_sub_charts); break; case E_PICTURES_SLIDER : elements_total=::ArraySize(m_wnd[index].m_pictures_slider); break; case E_TIME_EDIT : elements_total=::ArraySize(m_wnd[index].m_time_edits); break; case E_TEXT_BOX : elements_total=::ArraySize(m_wnd[index].m_text_boxes); break; case E_TREE_VIEW : elements_total=::ArraySize(m_wnd[index].m_treeview_lists); break; case E_FILE_NAVIGATOR : elements_total=::ArraySize(m_wnd[index].m_file_navigators); break; case E_TOOLTIP : elements_total=::ArraySize(m_wnd[index].m_tooltips); break; } //--- Return the number of controls of the specified type return(elements_total); }
To reduce the load on the CPU, it was necessary to add a few more arrays to the WindowElements structure, which will store the pointers to controls of the following categories.
- Array of main controls
- Array of controls with timers
- Array of controls that are visible and available for processing
- Array of controls with enabled auto-resizing along the X axis
- Array of controls with enabled auto-resizing along the Y axis
class CWndContainer { protected: ... //--- Structure of control arrays struct WindowElements { ... //--- Array of the main controls CElement *m_main_elements[]; //--- Timer controls CElement *m_timer_elements[]; //--- Controls that are currently visible and available CElement *m_available_elements[]; //--- Controls with auto-resizing along the X axis CElement *m_auto_x_resize_elements[]; //--- Controls with auto-resizing along the Y axis CElement *m_auto_y_resize_elements[]; ... }; //--- Array of arrays of controls for each window WindowElements m_wnd[]; ... };
The sizes of these arrays are obtained by the appropriate methods:
class CWndContainer { public: //--- The number of main controls int MainElementsTotal(const int window_index); //--- The number of controls with timers int TimerElementsTotal(const int window_index); //--- The number of controls with auto-resizing along the X axis int AutoXResizeElementsTotal(const int window_index); //--- The number of controls with auto-resizing along the Y axis int AutoYResizeElementsTotal(const int window_index); //--- The number of currently available controls int AvailableElementsTotal(const int window_index); };
The CWndContainer::AddToElementsArray() method adds pointers to the array of main controls. The shortened version of the method:
//+------------------------------------------------------------------+ //| Adds pointer to the controls array | //+------------------------------------------------------------------+ void CWndContainer::AddToElementsArray(const int window_index,CElementBase &object) { ... //--- Add to the array of the main controls last_index=ResizeArray(m_wnd[window_index].m_main_elements); m_wnd[window_index].m_main_elements[last_index]=::GetPointer(object); ... }
Arrays of other categories are generated in the CWndEvents class (see below). Separate methods are used in them for adding pointers.
class CWndContainer { protected: //--- Adds pointer to the array of controls with timers void AddTimerElement(const int window_index,CElement &object); //--- Adds pointer to the array of controls with auto-resizing along the X axis void AddAutoXResizeElement(const int window_index,CElement &object); //--- Adds pointer to the array of controls with auto-resizing along the Y axis void AddAutoYResizeElement(const int window_index,CElement &object); //--- Adds pointer to the array of currently available controls void AddAvailableElement(const int window_index,CElement &object); }; //+------------------------------------------------------------------+ //| Adds pointer to the array of controls with timers | //+------------------------------------------------------------------+ void CWndContainer::AddTimerElement(const int window_index,CElement &object) { int last_index=ResizeArray(m_wnd[window_index].m_timer_elements); m_wnd[window_index].m_timer_elements[last_index]=::GetPointer(object); } //+------------------------------------------------------------------+ //| Adds pointer to the array of controls with auto-resizing (X) | //+------------------------------------------------------------------+ void CWndContainer::AddAutoXResizeElement(const int window_index,CElement &object) { int last_index=ResizeArray(m_wnd[window_index].m_auto_x_resize_elements); m_wnd[window_index].m_auto_x_resize_elements[last_index]=::GetPointer(object); } //+------------------------------------------------------------------+ //| Adds pointer to the array of controls with auto-resizing (Y) | //+------------------------------------------------------------------+ void CWndContainer::AddAutoYResizeElement(const int window_index,CElement &object) { int last_index=ResizeArray(m_wnd[window_index].m_auto_y_resize_elements); m_wnd[window_index].m_auto_y_resize_elements[last_index]=::GetPointer(object); } //+------------------------------------------------------------------+ //| Adds pointer to the array of available controls | //+------------------------------------------------------------------+ void CWndContainer::AddAvailableElement(const int window_index,CElement &object) { int last_index=ResizeArray(m_wnd[window_index].m_available_elements); m_wnd[window_index].m_available_elements[last_index]=::GetPointer(object); }
There are also new methods for internal use in the CWndEvents class as well. Thus, the CWndEvents::Hide() method is required for hiding all controls of the graphical interface. It uses a double cycle: first the forms are hidden, and then the second cycle hides the controls attached to the form. Please note that in this method, the second cycle iterates over the array of controls, consisting of pointers to the main controls. The Hide() and Show() methods of controls are now arranged in such a way that they affect the entire chain of these methods in nested controls for the entire depth of the nesting.
//+------------------------------------------------------------------+ //| Class for event handling | //+------------------------------------------------------------------+ class CWndEvents : public CWndContainer { protected: //--- Hide all controls void Hide(); }; //+------------------------------------------------------------------+ //| Hide the controls | //+------------------------------------------------------------------+ void CWndEvents::Hide(void) { int windows_total=CWndContainer::WindowsTotal(); for(int w=0; w<windows_total; w++) { m_windows[w].Hide(); int main_total=MainElementsTotal(w); for(int e=0; e<main_total; e++) { CElement *el=m_wnd[w].m_main_elements[e]; el.Hide(); } } }
This also has a new CWndEvents::Show() method for showing the controls in the specified form. The window specified in the argument is shown first. Then, if the window is not minimized, all controls attached to this form are made visible. This cycle skips only controls that are (1) drop-down or (2) the ones with a Tabs control designated as their main control. The controls in tabs are later shown using the CWndEvents::ShowTabElements() method outside the cycle.
class CWndEvents : public CWndContainer { protected: //--- Show controls of the specified window void Show(const uint window_index); }; //+------------------------------------------------------------------+ //| Show controls of the specified window | //+------------------------------------------------------------------+ void CWndEvents::Show(const uint window_index) { //--- Show controls of the specified window m_windows[window_index].Show(); //--- If the window is not minimized if(!m_windows[window_index].IsMinimized()) { int main_total=MainElementsTotal(window_index); for(int e=0; e<main_total; e++) { CElement *el=m_wnd[window_index].m_main_elements[e]; //--- Show the control, if it is (1) not drop-down and (2) its main control is not tab if(!el.IsDropdown() && dynamic_cast<CTabs*>(el.MainPointer())==NULL) el.Show(); } //--- Show controls of the selected tabs only ShowTabElements(window_index); } }
The CWndEvents::Update() method will be required to redraw all controls of the graphical interface of the MQL application. This method can work in two modes: (1) fully redraw all controls or (2) apply the previously made changes. To fully redraw and update the graphical interface, it is necessary to pass the value true.
class CWndEvents : public CWndContainer { protected: //--- Redraw the controls void Update(const bool redraw=false); }; //+------------------------------------------------------------------+ //| Redraw the controls | //+------------------------------------------------------------------+ void CWndEvents::Update(const bool redraw=false) { int windows_total=CWndContainer::WindowsTotal(); for(int w=0; w<windows_total; w++) { //--- Redraw the controls int elements_total=CWndContainer::ElementsTotal(w); for(int e=0; e<elements_total; e++) { CElement *el=m_wnd[w].m_elements[e]; el.Update(redraw); } } }
We will discuss these methods a little later. For now, let us consider a number of methods designed for generating arrays for the aforementioned categories.
The previous version had a timer-based gradual change of controls' color when they were hovered by the mouse cursor. In order to reduce the volume and lower the consumption of resources, this superfluous feature has been removed. Therefore, the timer is not used in all controls of the current version of the library. It is present only in the fast scrolling of (1) the scrollbar thumbs, (2) the values in spin edit boxes and (3) the dates in the calendar. Therefore, only the appropriate controls are added to the corresponding array in the CWndEvents::FormTimerElementsArray() method (see the code listing below).
Since the pointers to controls are stored in arrays of the base type of controls (CElement), the dynamic type casting (dynamic_cast) is used here and in many other methods of the classes for determining the derived type of controls.
class CWndEvents : public CWndContainer { protected: //--- Generates the array of controls with timers void FormTimerElementsArray(void); }; //+------------------------------------------------------------------+ //| Generates the array of controls with timers | //+------------------------------------------------------------------+ void CWndEvents::FormTimerElementsArray(void) { int windows_total=CWndContainer::WindowsTotal(); for(int w=0; w<windows_total; w++) { int elements_total=CWndContainer::ElementsTotal(w); for(int e=0; e<elements_total; e++) { CElement *el=m_wnd[w].m_elements[e]; //--- if(dynamic_cast<CCalendar *>(el)!=NULL || dynamic_cast<CColorPicker *>(el)!=NULL || dynamic_cast<CListView *>(el)!=NULL || dynamic_cast<CTable *>(el)!=NULL || dynamic_cast<CTextBox *>(el)!=NULL || dynamic_cast<CTextEdit *>(el)!=NULL || dynamic_cast<CTreeView *>(el)!=NULL) { CWndContainer::AddTimerElement(w,el); } } } }
Now the timer has become much simpler: it no longer needs to check the entire list of controls, but only the ones that include this function:
//+------------------------------------------------------------------+ //| Checking events of all controls by timer | //+------------------------------------------------------------------+ void CWndEvents::CheckElementsEventsTimer(void) { int awi=m_active_window_index; int timer_elements_total=CWndContainer::TimerElementsTotal(awi); for(int e=0; e<timer_elements_total; e++) { CElement *el=m_wnd[awi].m_timer_elements[e]; if(el.IsVisible()) el.OnEventTimer(); } }
Handling the mouse over event is also required only for certain controls of the graphical interface. The array of controls available for handling such an event now excludes:
- CButtonsGroup — group of buttons;
- CFileNavigator — file navigator;
- CLineGraph — line chart;
- CPicture — picture;
- CPicturesSlider — picture slider;
- CProgressBar — progress bar;
- CSeparateLine — separation line;
- CStatusBar — status bar;
- CTabs — tabs;
- CTextLabel — text label.
All these controls are not highlighted when hovered by mouse. However, some of them have nested controls that are highlighted. But since the common array is used in the cycle for forming the array of available controls, the nested controls will participate in the selection as well. The array will pick all controls that are visible, available and not locked.
class CWndEvents : public CWndContainer { protected: //--- Generates the array of available controls void FormAvailableElementsArray(void); }; //+------------------------------------------------------------------+ //| Generates the array of available controls | //+------------------------------------------------------------------+ void CWndEvents::FormAvailableElementsArray(void) { //--- Window index int awi=m_active_window_index; //--- The total number of controls int elements_total=CWndContainer::ElementsTotal(awi); //--- Clear the array ::ArrayFree(m_wnd[awi].m_available_elements); //--- for(int e=0; e<elements_total; e++) { CElement *el=m_wnd[awi].m_elements[e]; //--- Add only the controls that are visible and available for processing if(!el.IsVisible() || !el.IsAvailable() || el.IsLocked()) continue; //--- Exclude the controls that do not require handling the mouseover events if(dynamic_cast<CButtonsGroup *>(el)==NULL && dynamic_cast<CFileNavigator *>(el)==NULL && dynamic_cast<CLineGraph *>(el)==NULL && dynamic_cast<CPicture *>(el)==NULL && dynamic_cast<CPicturesSlider *>(el)==NULL && dynamic_cast<CProgressBar *>(el)==NULL && dynamic_cast<CSeparateLine *>(el)==NULL && dynamic_cast<CStatusBar *>(el)==NULL && dynamic_cast<CTabs *>(el)==NULL && dynamic_cast<CTextLabel *>(el)==NULL) { AddAvailableElement(awi,el); } } }
It remains to consider the CWndEvents::FormAutoXResizeElementsArray() and CWndEvents::FormAutoYResizeElementsArray() methods, which generate arrays with pointers to controls that have the auto-resizing modes enabled. Such controls follow the sizes of the main controls they are attached to. Not all controls have a method code for auto-resizing. Here are the ones that have it:
Controls that have the code defined in the CElement::ChangeWidthByRightWindowSide() virtual method for auto-resizing the width:
- CButton — button.
- CFileNavigator — file navigator.
- CLineGraph — line chart.
- CListView — list view.
- CMenuBar — main menu.
- CProgressBar — progress bar.
- CStandardChart — standard chart.
- CStatusBar — status bar.
- CTable — table.
- CTabs — tabs.
- CTextBox — text edit box.
- CTextEdit — edit box.
- CTreeView — tree view.
Controls that have the code defined in the CElement::ChangeHeightByBottomWindowSide() virtual method for auto-resizing the height:
- CLineGraph — line chart.
- CListView — list view.
- CStandardChart — standard chart.
- CTable — table.
- CTabs — tabs.
- CTextBox — text edit box.
When creating arrays for these categories, it is checked whether the auto-resizing modes are enabled in these controls; if enabled, the controls are added to the array. The code of these will not be considered: similar methods have been discussed above.
Now let us find out when the arrays for the categories listed above are generated. In the main method for creating the graphical interface (that user creates on his own), once all specified controls are successfully created, it is now necessary to call only one method CWndEvents::CompletedGUI() to display them all on the chart. It signals that program that the creation of the graphical interface of the MQL application is completed.
Let us consider the CWndEvents::CompletedGUI() method in details. It calls all the methods described earlier in this section. First, all controls of the graphical interface are hidden. None of them is rendered yet. Therefore, to avoid their gradual sequential appearance, they need to be hidden before rendering. Next, the rendering itself takes place, and the latest changes are applied to each control. After that, it is necessary to display only the controls of the main window. Then the arrays of pointers to controls by categories are generates. At the end of the method, the chart is updated.
class CWndEvents : public CWndContainer { protected: //--- Finishing the creation of GUI void CompletedGUI(void); }; //+------------------------------------------------------------------+ //| Finishing the creation of GUI | //+------------------------------------------------------------------+ void CWndEvents::CompletedGUI(void) { //--- Leave, if there is no window yet int windows_total=CWndContainer::WindowsTotal(); if(windows_total<1) return; //--- Show the comment informing the user ::Comment("Update. Please wait..."); //--- Hide the controls Hide(); //--- Draw the controls Update(true); //--- Show the controls of the activated window Show(m_active_window_index); //--- Generate the array of controls with timers FormTimerElementsArray(); //--- Generates the array of visible and at the same time available controls FormAvailableElementsArray(); //--- Generate the arrays of controls with auto-resizing FormAutoXResizeElementsArray(); FormAutoYResizeElementsArray(); //--- Redraw the chart m_chart.Redraw(); //--- Clear the comment ::Comment(""); }
The CWndEvents::CheckElementsEvents() method for checking and handling the events of controls has been significantly modified. Let us dwell on this in more detail.
This method now has two blocks of event handling. One block is designed exclusively for handling the mouse cursor movement (CHARTEVENT_MOUSE_MOVE). Instead of looping through the list of all controls of the activated window, as it was before, the cycle now only iterates over controls available for processing. This is the reason why the array with the pointers to available controls was generated in the first place. The graphical interface of a large MQL application may have several hundreds and even thousands of controls, and only a few from the list can be visible and available at a time. This approach greatly saves the CPU resources.
Another modification is that now the checks (1) of the subwindow the form is located in and (2) the focus over the control are now performed in an external cycle, and not in the handlers of each class of controls. Thus, the checks relevant to each control are now located in the same place. This will be convenient in the future, in case it is necessary to make changes to the event handling algorithm.
All other types of event are handled in a separate block. The current version iterates over the entire list of controls of the graphical interface. All checks that were previously located in the classes of controls have also been moved to an external cycle.
At the very end of the method, the event is sent to the custom class of the MQL application.
//+------------------------------------------------------------------+ //| Checking control events | //+------------------------------------------------------------------+ void CWndEvents::CheckElementsEvents(void) { //--- Handling the event of moving the mouse cursor if(m_id==CHARTEVENT_MOUSE_MOVE) { //--- Leave, if the form is in another subwindow of the chart if(!m_windows[m_active_window_index].CheckSubwindowNumber()) return; //--- Check only the available controls int available_elements_total=CWndContainer::AvailableElementsTotal(m_active_window_index); for(int e=0; e<available_elements_total; e++) { CElement *el=m_wnd[m_active_window_index].m_available_elements[e]; //--- Checking the focus over controls el.CheckMouseFocus(); //--- Handling the event el.OnEvent(m_id,m_lparam,m_dparam,m_sparam); } } //--- All events, except the mouse cursor movement else { int elements_total=CWndContainer::ElementsTotal(m_active_window_index); for(int e=0; e<elements_total; e++) { //--- Check only the available controls CElement *el=m_wnd[m_active_window_index].m_elements[e]; if(!el.IsVisible() || !el.IsAvailable() || el.IsLocked()) continue; //--- Handling the event in the control's event handler el.OnEvent(m_id,m_lparam,m_dparam,m_sparam); } } //--- Forwarding the event to the application file OnEvent(m_id,m_lparam,m_dparam,m_sparam); }
The CWndEvents::FormAvailableElementsArray() method for generating the array of controls, that are visible and at same time available for processing, is called in the following cases:
- Opening a dialog box. Once a dialog box is opened, the ON_OPEN_DIALOG_BOX event is generated, which is handled in the CWndEvents::OnOpenDialogBox() method. After handling this event, it is necessary to generate the array of available controls for the opened window.
- Changes in the graphical interface. Any change in the graphical interface caused by interaction generates the ON_CHANGE_GUI event. It is handled by a new private method CWndEvents::OnChangeGUI(). Here, when the ON_CHANGE_GUI event arrives, the array of available controls is generated first. Then all tooltips are moved to the top layer. At the end of the method, the chart is redrawn to display the latest changes.
class CWndEvents : public CWndContainer { private: //--- Changes in the graphical interface bool OnChangeGUI(void); }; //+------------------------------------------------------------------+ //| Event of changes in the graphical interface | //+------------------------------------------------------------------+ bool CWndEvents::OnChangeGUI(void) { //--- If the signal is about the change in the graphical interface if(m_id!=CHARTEVENT_CUSTOM+ON_CHANGE_GUI) return(false); //--- Generates the array of visible and at the same time available controls FormAvailableElementsArray(); //--- Move tooltips to the top layer ResetTooltips(); //--- Redraw the chart m_chart.Redraw(); return(true); }
Next, consider the handling of an event with the ON_SET_AVAILABLE identifier for determining controls that will be available for processing.
The CWndEvents::OnSetAvailable() method has been implemented for handling the ON_SET_AVAILABLE event. But before proceeding to the description of its code, it is necessary to consider a number of auxiliary methods. There are 10 graphical interface controls that generate events with such identifier. They all have the means to determine their activated state. Let us name them:
- Main menu — CMenuBar::State().
- Menu item — CMenuItem::GetContextMenuPointer().IsVisible().
- Split button — CSplitButton::GetContextMenuPointer().IsVisible().
- Combobox — CComboBox::GetListViewPointer().IsVisible().
- Drop-down calendar — DropCalendar::GetCalendarPointer().IsVisible().
- Scrollbar — CScroll::State().
- Table — CTable::ColumnResizeControl().
- Numerical slider — CSlider::State().
- Tree view — CTreeView::GetMousePointer().State().
- Standard chart — CStandartChart::GetMousePointer().IsVisible().
Each of these controls has private arrays in the CWndContainer class. The CWndEvents class implements methods for determining which of the controls is currently activated. All these methods return the index of the activated control in its private array.
class CWndEvents : public CWndContainer { private: //--- Returns the index of the activated main menu int ActivatedMenuBarIndex(void); //--- Returns the index of the activated menu item int ActivatedMenuItemIndex(void); //--- Returns the index of the activated split button int ActivatedSplitButtonIndex(void); //--- Returns the index of the activated combobox int ActivatedComboBoxIndex(void); //--- Returns the index of the activated drop-down calendar int ActivatedDropCalendarIndex(void); //--- Returns the index of the activated scrollbar int ActivatedScrollIndex(void); //--- Returns the index of the activated table int ActivatedTableIndex(void); //--- Returns the index of the activated slider int ActivatedSliderIndex(void); //--- Returns the index of the activated tree view int ActivatedTreeViewIndex(void); //--- Returns the index of the activated subchart int ActivatedSubChartIndex(void); };
As the only different in most of these methods lies in the conditions for determining the states of the controls, only the code for one of them will be considered. The listing below shows the code of the CWndEvents::ActivatedTreeViewIndex() method, which returns the index of the activated tree view. If this type of control has the tab items mode enabled, the check is rejected.
//+------------------------------------------------------------------+ //| Returns the index of the activated tree view | //+------------------------------------------------------------------+ int CWndEvents::ActivatedTreeViewIndex(void) { int index=WRONG_VALUE; //--- int total=ElementsTotal(m_active_window_index,E_TREE_VIEW); for(int i=0; i<total; i++) { CTreeView *el=m_wnd[m_active_window_index].m_treeview_lists[i]; //--- Go to the next, if the tabs mode is enabled if(el.TabItemsMode()) continue; //--- If in the process of changing the width of lists if(el.GetMousePointer().State()) { index=i; break; } } return(index); }
The CWndEvents::SetAvailable() method is designed for setting the availability states of controls. As arguments, it is necessary to pass (1) the required form index and (2) the state to set for the controls.
If it is necessary to make all controls unavailable, simply iterate over them in a cycle and set the value false.
If it is necessary to make the controls available, then the overloaded CTreeView::IsAvailable() method of the same name is called for the tree views, which contains two modes for setting the state: (1) only for the main control and (2) for all controls for the full depth of nesting. Therefore, the dynamic type casting is used here to get the pointer to control of the derived control class.
class CWndEvents : public CWndContainer { protected: //--- Sets the availability states of controls void SetAvailable(const uint window_index,const bool state); }; //+------------------------------------------------------------------+ //| Sets the availability states of controls | //+------------------------------------------------------------------+ void CWndEvents::SetAvailable(const uint window_index,const bool state) { //--- Get the number of the main controls int main_total=MainElementsTotal(window_index); //--- If it is necessary to make the controls unavailable if(!state) { m_windows[window_index].IsAvailable(state); for(int e=0; e<main_total; e++) { CElement *el=m_wnd[window_index].m_main_elements[e]; el.IsAvailable(state); } } else { m_windows[window_index].IsAvailable(state); for(int e=0; e<main_total; e++) { CElement *el=m_wnd[window_index].m_main_elements[e]; //--- If it is a tree view if(dynamic_cast<CTreeView*>(el)!=NULL) { CTreeView *tv=dynamic_cast<CTreeView*>(el); tv.IsAvailable(true); continue; } //--- If it is a file navigator if(dynamic_cast<CFileNavigator*>(el)!=NULL) { CFileNavigator *fn =dynamic_cast<CFileNavigator*>(el); CTreeView *tv =fn.GetTreeViewPointer(); fn.IsAvailable(state); tv.IsAvailable(state); continue; } //--- Make the control available el.IsAvailable(state); } } }
Menu items with context menus attached required a method, which would allow looping through the entire depth of opened context menus, accessing them. In this case, it will be necessary to make the context menus available for processing. This will be implemented recursively.
Below is the code of the CWndEvents::CheckContextMenu() method. First, pass an object of the menu item type and attempt to get the pointer to the context menu. If the pointer is correct, check if this context menu is opened. If so, set the availability flag to it. Then set the availability status to all items of this menu in a cycle. At the same time, check each item if it has a context menu using the CWndEvents::CheckContextMenu() method.
class CWndEvents : public CWndContainer { private: //--- Checks and makes the context menu available void CheckContextMenu(CMenuItem &object); }; //+------------------------------------------------------------------+ //| Recursively check and make the context menus available | //+------------------------------------------------------------------+ void CWndEvents::CheckContextMenu(CMenuItem &object) { //--- Getting the context menu pointer CContextMenu *cm=object.GetContextMenuPointer(); //--- Leave, if there is no context menu in the item if(::CheckPointer(cm)==POINTER_INVALID) return; //--- Leave, if there is a context menu, but it is hidden if(!cm.IsVisible()) return; //--- Set the available control signs cm.IsAvailable(true); //--- int items_total=cm.ItemsTotal(); for(int i=0; i<items_total; i++) { //--- Set the available control signs CMenuItem *mi=cm.GetItemPointer(i); mi.IsAvailable(true); //--- Check if this item has a context menu CheckContextMenu(mi); } }
Now let us consider the CWndEvents::OnSetAvailable() method, which handles the event for determining the available controls.
If a custom event with the ON_SET_AVAILABLE identifier is received, it is first necessary to determine if there are currently activated controls. The local variables store the indexes of the activated controls for quick access to their private arrays.
If a signal to determine the available controls is received, then first disable the access in the entire list. If the signal is to restore, then after checking for absence of activated controls, the access is restored in the entire list and the program leaves the method.
If the program reaches the next code block in this method, this means that it is either (1) a signal to determine the available controls, or (2) to restore, but there is an activated drop-down calendar. The second situation may occur when opening a drop-down calendar with an activated combobox, where the drop-down list was closed.
If one of the described conditions is met, attempt to get the pointer to the activated control. If the pointer was not received, the program leaves the method.
If the pointer was obtained, the control is made available. For some controls, there are nuances of how this is done. Here are all such cases:
- Main menu (CMenuBar). If it is activated, then it is necessary to make available all open context menus related to it. For this purpose, the recursive method CWndEvents::CheckContextMenu() has been considered in the code listing above.
- Menu item (CMenuItem). Menu items can be independent controls, to which context menus can be attached. Therefore, if such a control is activated, the control itself (menu item) is also made available here, as well as all context menus opened from it.
- Scrollbar (CScroll). If a scrollbar is activated (thumb in the process of moving), then make available all the controls, starting from the first. For example, if the scrollbar is attached to a list view, then the list view and all its controls will become available, for the full depth of nesting.
- Tree view (CTreeView). This control can be activated when the width of its lists is being changed. It is necessary to exclude the processing of list views on mouseover, and make the tree view itself available for processing.
Below is the code of the CWndEvents::OnSetAvailable() method.
class CWndEvents : public CWndContainer { private: //--- Determine the available controls bool OnSetAvailable(void); }; //+------------------------------------------------------------------+ //| Event for determining the available controls | //+------------------------------------------------------------------+ bool CWndEvents::OnSetAvailable(void) { //--- If the signal is about changing the availability of controls if(m_id!=CHARTEVENT_CUSTOM+ON_SET_AVAILABLE) return(false); //--- Signal to set/restore bool is_restore=(bool)m_dparam; //--- Determine the active controls int mb_index =ActivatedMenuBarIndex(); int mi_index =ActivatedMenuItemIndex(); int sb_index =ActivatedSplitButtonIndex(); int cb_index =ActivatedComboBoxIndex(); int dc_index =ActivatedDropCalendarIndex(); int sc_index =ActivatedScrollIndex(); int tl_index =ActivatedTableIndex(); int sd_index =ActivatedSliderIndex(); int tv_index =ActivatedTreeViewIndex(); int ch_index =ActivatedSubChartIndex(); //--- If the signal is to determine the available controls, disable access first if(!is_restore) SetAvailable(m_active_window_index,false); //--- Restore only if there are no activated items else { if(mb_index==WRONG_VALUE && mi_index==WRONG_VALUE && sb_index==WRONG_VALUE && dc_index==WRONG_VALUE && cb_index==WRONG_VALUE && sc_index==WRONG_VALUE && tl_index==WRONG_VALUE && sd_index==WRONG_VALUE && tv_index==WRONG_VALUE && ch_index==WRONG_VALUE) { SetAvailable(m_active_window_index,true); return(true); } } //--- If (1) the signal is to disable access or (2) to restore the drop-down calendar if(!is_restore || (is_restore && dc_index!=WRONG_VALUE)) { CElement *el=NULL; //--- Main menu if(mb_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_menu_bars[mb_index]; } //--- Menu item else if(mi_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_menu_items[mi_index]; } //--- Split button else if(sb_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_split_buttons[sb_index]; } //--- Drop-down calendar without a drop-down list else if(dc_index!=WRONG_VALUE && cb_index==WRONG_VALUE) { el=m_wnd[m_active_window_index].m_drop_calendars[dc_index]; } //--- Drop-down list else if(cb_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_combo_boxes[cb_index]; } //--- Scrollbar else if(sc_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_scrolls[sc_index]; } //--- Table else if(tl_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_tables[tl_index]; } //--- Slider else if(sd_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_sliders[sd_index]; } //--- Tree view else if(tv_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_treeview_lists[tv_index]; } //--- Subchart else if(ch_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_sub_charts[ch_index]; } //--- Leave, if the control pointer is not received if(::CheckPointer(el)==POINTER_INVALID) return(true); //--- Block for the main menu if(mb_index!=WRONG_VALUE) { //--- Make the main menu and its visible context menus available el.IsAvailable(true); //--- CMenuBar *mb=dynamic_cast<CMenuBar*>(el); int items_total=mb.ItemsTotal(); for(int i=0; i<items_total; i++) { CMenuItem *mi=mb.GetItemPointer(i); mi.IsAvailable(true); //--- Checks and makes the context menu available CheckContextMenu(mi); } } //--- Block for the menu item if(mi_index!=WRONG_VALUE) { CMenuItem *mi=dynamic_cast<CMenuItem*>(el); mi.IsAvailable(true); //--- Checks and makes the context menu available CheckContextMenu(mi); } //--- Block for the scrollbar else if(sc_index!=WRONG_VALUE) { //--- Make available starting from the main node el.MainPointer().IsAvailable(true); } //--- Block for the tree view else if(tv_index!=WRONG_VALUE) { //--- Lock all controls except the main control CTreeView *tv=dynamic_cast<CTreeView*>(el); tv.IsAvailable(true,true); int total=tv.ElementsTotal(); for(int i=0; i<total; i++) tv.Element(i).IsAvailable(false); } else { //--- Make the control available el.IsAvailable(true); } } //--- return(true); }
Application for testing the controls
An MQL application has been implemented for testing purposes. Its graphical interface contains all the controls of the library. This how it is looks:
Fig. 12. Graphical interface of the test MQL application.
You can download it at the end of the article for a more detailed study.
Conclusion
This version of the library has significant differences from the one presented in the article Graphical Interfaces X: Text selection in the Multiline Text box (build 13). A lot of work was done, which affected almost all files of the library. Now all controls of the library are drawn on separate objects. Code readability has improved, the code volume has decreased by approximately 30%, and its features have expanded. A number of other errors and flaws reported by users have been fixed.
If you have already started creating your MQL applications using the previous version of the library, it is recommended that you first download the new version to a separately installed copy of the MetaTrader 5 terminal in order to study and thoroughly test the library.
The library for creating graphical interfaces at the current stage of development looks like in the schematic below. This is not the final version of the library: it will evolve and improve in the future.
Fig. 13. Library structure at the current stage of development
If you have questions on using the material presented in those files, you can refer to the detailed description of the library development in one of the articles of this 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/3366
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
Hi, The set of controls is really nice and helpful, but it would be even better if you can handle higher DPI setting in a better way.
Obviously, the bitmaps will display correctly, but the text does not. ...
I would say the text is scaled properly, unlike all the rest of the graphics.
I would say the text is scaled properly, unlike all the rest of the graphics.
That depends how you look at it. You can either scale up the bitmaps to match the font size of scale down the font size to match the size of the bitmaps.
Scaling up bitmaps is not really something you want to do because unlike vector graphics bitmaps will loose quality when scaled up. The font on the other hand is vector based so can be scaled up and down without loss in quality.
Obviously the bast way to cope with the problem would be to use vector graphics, but I don't think the platform supports it (although I may be wrong here as I don't really do graphics in metatrader).
Personally I scale down the font proportionally to match the DPI.
Anyway, it would be nice if the controls could handle this by default so we don't need to worry about it.
That depends how you look at it. You can either scale up the bitmaps to match the font size of scale down the font size to match the size of the bitmaps.
Scaling up bitmaps is not really something you want to do because unlike vector graphics bitmaps will loose quality when scaled up. The font on the other hand is vector based so can be scaled up and down without loss in quality.
Obviously the bast way to cope with the problem would be to use vector graphics, but I don't think the platform supports it (although I may be wrong here as I don't really do graphics in metatrader).
Personally I scale down the font proportionally to match the DPI.
Anyway, it would be nice if the controls could handle this by default so we don't need to worry about it.
I do not contradict the picture scaling difficulties, I just can spot that the font size change corresponds to the scaled Metatrader application GUI. So if you decrease the font size, you could make the letters rather tiny on the hi-res monitor.
I do not contradict the picture scaling difficulties, I just can spot that the font size change corresponds to the scaled Metatrader application GUI. So if you decrease the font size, you could make the letters rather tiny on the hi-res monitor.
Yes. I agree. It would be better to have the graphics match the size of the font which indeed matches the scaled up MT5 GUI. I was just looking for a way to have this better looking in a way that the font size matches the rest of the graphics.
I guess this is the best we are going to get in a mixed environment of raster & vector graphics.
Technically speaking it should as OBJ-BITMAP_LABEL is also available in MQL4, but you would need to adjust the library code as there are a few places where the code will just not compile using the MQL4 compiler.
I'll try to play around with the library and I'll let you know, if I'm able to somehow port it to MQL4. You can also try by yourself. Just rename the example EA's .mq5 extension to .mq4 and try to compile the code.
Obviously, you will get compilation errors however you can check where and what they are.
Hi Artur,
I know this is an old post, I was wondering how you went with converting this to MT4.. Any luck?
thanks