DoEasy. Controls (Part 1): First steps
Contents
Concept
The article starts a new series dedicated to creating controls in Windows Forms style. Of course, it is impossible to reproduce all elements included in the list of controls in MS Visual Studio. I am going to implement the most popular elements for developing app GUIs using MQL5.
The reason I have switched to a new topic without completing the previous ones was the need to use the controls to continue the development of the library graphical objects covered in the previous topics. It is already becoming difficult to manage things without controls. Therefore, I will create all possible controls in Windows Forms style. Then I will get back to the previous topics while having all the necessary development tools.
If we open the panel of elements in MS Visual Studio, we will see the list of control groups:
- All Windows Forms — all forms available for implementation
- Standard controls
- Containers
- Menus and toolbars
- Data
- Components
- Dialog boxes
These are not all the groups available in the list of MS Visual Studio element panel. Each such group contains a large set of elements. Not all of them are necessary for the library. I will focus on the essential ones.
I will start with the Panel element since it serves as the basis for window elements. Besides, the panel is a container for storing other controls, while the panel with all stored elements can, in turn, be placed into the parent panel, while the latter can also be an object inside another panel etc.
We already have the class of a graphical element object on canvas, which is a parent class for all other graphical objects based on CCanvas class. The form class object is based on a graphical element. The form object already has a set of functions for manipulating and moving it. The panel object will be created based on a form object. The new properties will be added to the form object to implement its functionality.
The panel will have the ability to store any controls I am going to create within the current section of the library development description. The panel will also enable us to implement basic and dialog windows of an application working in the terminal.
Before developing the panel class, we should improve the already developed library object classes. After all, I have not finished my work on the previous topics. I am going to gradually finalize the existing library objects and correct the detected errors.
Improving library classes
The last update of the terminal version 3260 features new properties for a symbol and account:
- MQL5: Added SYMBOL_SUBSCRIPTION_DELAY value into the ENUM_SYMBOL_INFO_INTEGER enumeration for the delay in quotes delivery for specific symbols.
It is only used for subscription-based trading symbols. The delay is usually applicable to data provided in trial mode.
The property can only be requested for symbols selected in the Market Watch. Otherwise, the ERR_MARKET_NOT_SELECTED (4302) error will be returned. - MQL5: Added ACCOUNT_HEDGE_ALLOWED property into the ENUM_ACCOUNT_INFO_INTEGER enumeration — enables the opening of opposite positions and pending orders. The property is only used for hedging accounts to comply with specific regulatory requirements, according to which an account cannot have opposite positions for the same symbol, while same-direction positions are allowed.
If this option is disabled, accounts are not allowed to have opposite-direction positions and orders for the same financial instrument. For example, if the account has a Buy position, then a user cannot open a Sell position or place a pending sell order. If the user tries to perform such an operation, the TRADE_RETCODE_HEDGE_PROHIBITED error will be returned.
Let's add these properties to the symbol and library account objects.
In \MQL5\Include\DoEasy\Data.mqh, add the new message indices:
//+------------------------------------------------------------------+ //| List of the library's text message indices | //+------------------------------------------------------------------+ enum ENUM_MESSAGES_LIB { MSG_LIB_PARAMS_LIST_BEG=ERR_USER_ERROR_FIRST, // Beginning of the parameter list MSG_LIB_PARAMS_LIST_END, // End of the parameter list MSG_LIB_PROP_NOT_SUPPORTED, // Property not supported MSG_LIB_PROP_NOT_SUPPORTED_MQL4, // Property not supported in MQL4 MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_2155, // Property not supported in MetaTrader 5 versions lower than 2155 MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3245, // Property not supported in MetaTrader 5 versions lower than 3245 MSG_LIB_PROP_NOT_SUPPORTED_POSITION, // Property not supported for position
...
MSG_SYM_PROP_BACKGROUND_COLOR, // Background color of the symbol in Market Watch MSG_SYM_PROP_SUBSCRIPTION_DELAY, // Delay for quotes passed by symbol for instruments working on subscription basis //---
...
MSG_ACC_PROP_FIFO_CLOSE, // Flag of a position closure by FIFO rule only MSG_ACC_PROP_HEDGE_ALLOWED, // Permission to open opposite positions and set pending orders //--- MSG_ACC_PROP_BALANCE, // Account balance
...
MSG_GRAPH_ELEMENT_TYPE_FORM, // Form MSG_GRAPH_ELEMENT_TYPE_WINDOW, // Window MSG_GRAPH_ELEMENT_TYPE_PANEL, // Panel control MSG_GRAPH_OBJ_BELONG_PROGRAM, // Graphical object belongs to a program MSG_GRAPH_OBJ_BELONG_NO_PROGRAM, // Graphical object does not belong to a program //---
and the text messages corresponding to the newly added indices:
{"Свойство не поддерживается в MetaTrader5 версии ниже 2155","The property is not supported in MetaTrader5, build lower than 2155"}, {"Свойство не поддерживается в MetaTrader5 версии ниже 3245","The property is not supported in MetaTrader5, build lower than 3245"}, {"Свойство не поддерживается у позиции","Property not supported for position"},
...
{"Цвет фона символа в Market Watch","Background color of the symbol in Market Watch"}, {"Размер задержки у котировок, передаваемых по символу, для инструментов, работающих по подписке","Delay size for quotes transmitted per symbol for instruments working by subscription"}, {"Максимальный Bid за день","Maximum Bid of the day"},
...
{"Тип торгового сервера","Type of trading server"}, {"Признак закрытия позиций только по правилу FIFO","Sign of closing positions only according to the FIFO rule"}, {"Разрешение на открытие встречных позиций и отложенных ордеров","Permission to open opposite positions and pending orders"}, //--- {"Баланс счета","Account balance"},
...
{"Форма","Form"}, {"Окно","Window"}, {"Элемент управления \"Panel\"","Control element \"Panel\""}, {"Графический объект принадлежит программе","The graphic object belongs to the program"}, {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},
Any panel object created in the current article is to have the default parameters for text messages displayed on it. These parameters will be used for any text displayed on the panel or on its descendant objects, or attached to the panel if it is considered to be a container for these objects. We need to set default values for the font name, size and color.
Open \MQL5\Include\DoEasy\Defines.mqh and add the new macro substitutions to it for these text properties on the panel:
//--- Canvas parameters #define PAUSE_FOR_CANV_UPDATE (16) // Canvas update frequency #define CLR_CANV_NULL (0x00FFFFFF) // Zero for the canvas with the alpha channel #define CLR_FORE_COLOR (C'0x2D,0x43,0x48') // Default color for texts of objects on canvas #define DEF_FONT ("Calibri") // Default font #define DEF_FONT_SIZE (8) // Default font size #define OUTER_AREA_SIZE (16) // Size of one side of the outer area around the form workspace //--- Graphical object parameters
Add a new type to the list of library object types:
//+------------------------------------------------------------------+ //| List of library object types | //+------------------------------------------------------------------+ enum ENUM_OBJECT_DE_TYPE { //--- Graphics OBJECT_DE_TYPE_GBASE = COLLECTION_ID_LIST_END+1, // "Base object of all library graphical objects" object type OBJECT_DE_TYPE_GELEMENT, // "Graphical element" object type OBJECT_DE_TYPE_GFORM, // Form object type OBJECT_DE_TYPE_GFORM_CONTROL, // "Form for managing pivot points of graphical object" object type OBJECT_DE_TYPE_GSHADOW, // Shadow object type //--- WinForms OBJECT_DE_TYPE_GWF_PANEL, // WinForms Panel object type //--- Animation OBJECT_DE_TYPE_GFRAME, // "Single animation frame" object type OBJECT_DE_TYPE_GFRAME_TEXT, // "Single text animation frame" object type OBJECT_DE_TYPE_GFRAME_QUAD, // "Single rectangular animation frame" object type OBJECT_DE_TYPE_GFRAME_GEOMETRY, // "Single geometric animation frame" object type OBJECT_DE_TYPE_GANIMATIONS, // "Animations" object type //--- Managing graphical objects
In this section (WinForms), I will add new object types as they are created.
In the enumeration of account integer properties, add a new property and increase the number of integer object properties from 11 to 12:
//+------------------------------------------------------------------+ //| Account integer properties | //+------------------------------------------------------------------+ enum ENUM_ACCOUNT_PROP_INTEGER { ... ACCOUNT_PROP_FIFO_CLOSE, // Flag of a position closure by FIFO rule only ACCOUNT_PROP_HEDGE_ALLOWED // Permission to open opposite positions and set pending orders }; #define ACCOUNT_PROP_INTEGER_TOTAL (12) // Total number of integer properties #define ACCOUNT_PROP_INTEGER_SKIP (0) // Number of integer account properties not used in sorting //+------------------------------------------------------------------+
Add the new property to the list of possible account sorting criteria:
//+------------------------------------------------------------------+ //| Possible account sorting criteria | //+------------------------------------------------------------------+ #define FIRST_ACC_DBL_PROP (ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_INTEGER_SKIP) #define FIRST_ACC_STR_PROP (ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_INTEGER_SKIP+ACCOUNT_PROP_DOUBLE_TOTAL-ACCOUNT_PROP_DOUBLE_SKIP) enum ENUM_SORT_ACCOUNT_MODE { ... SORT_BY_ACCOUNT_FIFO_CLOSE, // Sort by the flag of a position closure by FIFO rule only SORT_BY_ACCOUNT_HEDGE_ALLOWED, // Sort by permission to open opposite positions and set pending orders //--- Sort by real properties SORT_BY_ACCOUNT_BALANCE = FIRST_ACC_DBL_PROP, // Sort by an account balance in the deposit currency SORT_BY_ACCOUNT_CREDIT, // Sort by credit in a deposit currency ... SORT_BY_ACCOUNT_COMPANY // Sort by a name of a company serving an account }; //+------------------------------------------------------------------+
In the enumeration of symbol integer properties, add a new property and increase the number of integer properties from 40 to 41:
//+------------------------------------------------------------------+ //| Symbol integer properties | //+------------------------------------------------------------------+ enum ENUM_SYMBOL_PROP_INTEGER { //--- ... SYMBOL_PROP_OPTION_MODE, // Option type (from the ENUM_SYMBOL_OPTION_MODE enumeration) SYMBOL_PROP_OPTION_RIGHT, // Option right (Call/Put) (from the ENUM_SYMBOL_OPTION_RIGHT enumeration) SYMBOL_PROP_SUBSCRIPTION_DELAY, // Delay for quotes passed by symbol for instruments working on subscription basis //--- skipped property SYMBOL_PROP_BACKGROUND_COLOR // The color of the background used for the symbol in Market Watch }; #define SYMBOL_PROP_INTEGER_TOTAL (41) // Total number of integer properties #define SYMBOL_PROP_INTEGER_SKIP (1) // Number of symbol integer properties not used in sorting //+------------------------------------------------------------------+
Add sorting by a new property to the enumeration of possible symbol sorting criteria:
//+------------------------------------------------------------------+ //| Possible symbol sorting criteria | //+------------------------------------------------------------------+ #define FIRST_SYM_DBL_PROP (SYMBOL_PROP_INTEGER_TOTAL-SYMBOL_PROP_INTEGER_SKIP) #define FIRST_SYM_STR_PROP (SYMBOL_PROP_INTEGER_TOTAL-SYMBOL_PROP_INTEGER_SKIP+SYMBOL_PROP_DOUBLE_TOTAL-SYMBOL_PROP_DOUBLE_SKIP) enum ENUM_SORT_SYMBOLS_MODE { ... SORT_BY_SYMBOL_OPTION_MODE, // Sort by option type (from the ENUM_SYMBOL_OPTION_MODE enumeration) SORT_BY_SYMBOL_OPTION_RIGHT, // Sort by option right (Call/Put) (from the ENUM_SYMBOL_OPTION_RIGHT enumeration) SORT_BY_SYMBOL_SUBSCRIPTION_DELAY, // Sort by delay for quotes passed by symbol for instruments working on subscription basis
Add a new element type to the list of graphical element types:
//+------------------------------------------------------------------+ //| The list of graphical element types | //+------------------------------------------------------------------+ enum ENUM_GRAPH_ELEMENT_TYPE { GRAPH_ELEMENT_TYPE_STANDARD, // Standard graphical object GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED, // Extended standard graphical object GRAPH_ELEMENT_TYPE_ELEMENT, // Element GRAPH_ELEMENT_TYPE_SHADOW_OBJ, // Shadow object GRAPH_ELEMENT_TYPE_FORM, // Form GRAPH_ELEMENT_TYPE_WINDOW, // Window //--- WinForms GRAPH_ELEMENT_TYPE_PANEL, // Windows Forms Panel }; //+------------------------------------------------------------------+
When creating each subsequent control, its type will be entered in this subsection of the enumeration (WinForms).
If another object (bigger than the container panel) is added to the panel object and the auto change of its size is allowed for the panel, there are two size change options:
- increasing the panel size only
- increasing and decreasing the panel size
In the first case, the panel sides that do not include the object placed in it are enlarged so that the object fits completely into it. In the second case, in addition to the action described above, it allows decreasing the sides that are larger than the object placed inside.
Right after the enumeration of possible graphical object sorting criteria, add a new enumeration setting the modes of auto changing the interface element size:
//+------------------------------------------------------------------+ //| Mode of automatic interface element resizing | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_AUTO_SIZE_MODE { CANV_ELEMENT_AUTO_SIZE_MODE_GROW, // Increase only CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK, // Increase and decrease }; //+------------------------------------------------------------------+
When placing an object inside the panel, the object can be attached to any side of its container — top, bottom, right and left. In this case, the closest side "sticks" to the corresponding side of the container object and the dimensions of the attached object are stretched to the container sides perpendicular to the side the object is attached to. For example, if the object is attached to the top edge of its container, then the object's top edge is pulled to the top edge of the container, while the left and right sides of the object are stretched to the corresponding sides of the container. The object height does not change. Attaching to other sides of the container works similarly.
Add the new enumeration right after enumerating the modes of size auto change:
//+------------------------------------------------------------------+ //| Control borders bound to the container | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_DOCK_MODE { CANV_ELEMENT_DOCK_MODE_TOP, // Attaching to the top and stretching along the container width CANV_ELEMENT_DOCK_MODE_BOTTOM, // Attaching to the bottom and stretching along the container width CANV_ELEMENT_DOCK_MODE_LEFT, // Attaching to the left and stretching along the container height CANV_ELEMENT_DOCK_MODE_RIGHT, // Attaching to the right and stretching along the container height CANV_ELEMENT_DOCK_MODE_FILL, // Stretching along the entire container width and height CANV_ELEMENT_DOCK_MODE_NONE, // Attached to the specified coordinates, size does not change }; //+------------------------------------------------------------------+
Apart from the four above mentioned methods of attaching an object to a container, there are another two: filling (object size is adjusted to the container size) and no attachment (an object is attached to specified coordinates only inside its container while its size remains unchanged).
If a control has the functionality of interacting with a user, such an object may be considered inaccessible for interaction under certain conditions (for example, a button is inactive). Add a new graphical element property to indicate the possibility of interaction with the element.
In the enumeration of graphical element integer properties, add a new property and increase the number of object integer properties from 24 to 25:
//+------------------------------------------------------------------+ //| Integer properties of the graphical element on the canvas | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_PROP_INTEGER { ... CANV_ELEMENT_PROP_ZORDER, // Priority of a graphical object for receiving the event of clicking on a chart CANV_ELEMENT_PROP_ENABLED, // Element availability flag }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (25) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting //+------------------------------------------------------------------+
Add sorting by new property to the enumeration of possible criteria of sorting graphical elements on canvas:
//+------------------------------------------------------------------+ //| Possible sorting criteria of graphical elements on the canvas | //+------------------------------------------------------------------+ #define FIRST_CANV_ELEMENT_DBL_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP) #define FIRST_CANV_ELEMENT_STR_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP) enum ENUM_SORT_CANV_ELEMENT_MODE { ... SORT_BY_CANV_ELEMENT_ZORDER, // Sort by the priority of a graphical object for receiving the event of clicking on a chart SORT_BY_CANV_ELEMENT_ENABLED, // Sort by the element availability flag ... }; //+------------------------------------------------------------------+
Since we now have the new symbol and account properties, we need to improve the object classes.
Open \MQL5\Include\DoEasy\Objects\Accounts\Account.mqh and make improvements in the account object class.
Add a new integer property to the object property structure:
//+------------------------------------------------------------------+ //| Account class | //+------------------------------------------------------------------+ class CAccount : public CBaseObjExt { private: struct SData { //--- Account integer properties ... bool fifo_close; // ACCOUNT_FIFO_CLOSE (The flag indicating that positions can be closed only by the FIFO rule) bool hedge_allowed; // ACCOUNT_HEDGE_ALLOWED (Permission to open opposite positions and set pending orders) ... }; SData m_struct_obj; // Account object structure uchar m_uchar_array[]; // uchar array of the account object structure //--- Object properties
Add a new method in the block of methods for a simplified access to account object properties:
//+------------------------------------------------------------------+ //| Methods of a simplified access to the account object properties | //+------------------------------------------------------------------+ //--- Return the account's integer properties ... bool FIFOClose(void) const { return (bool)this.GetProperty(ACCOUNT_PROP_FIFO_CLOSE); } bool IsHedge(void) const { return this.MarginMode()==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING; } bool HedgeAllowed(void) const { return (bool)this.GetProperty(ACCOUNT_PROP_HEDGE_ALLOWED); } ...
The method simply returns the value stored in the object property array.
Add the value to the array of object properties in the class constructor:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CAccount::CAccount(void) { this.m_type=OBJECT_DE_TYPE_ACCOUNT; //--- Initialize control data this.SetControlDataArraySizeLong(ACCOUNT_PROP_INTEGER_TOTAL); this.SetControlDataArraySizeDouble(ACCOUNT_PROP_DOUBLE_TOTAL); this.ResetChangesParams(); this.ResetControlsParams(); ... this.m_long_prop[ACCOUNT_PROP_SERVER_TYPE] = (::TerminalInfoString(TERMINAL_NAME)=="MetaTrader 5" ? 5 : 4); this.m_long_prop[ACCOUNT_PROP_FIFO_CLOSE] = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<2155 ? false : ::AccountInfoInteger(ACCOUNT_FIFO_CLOSE) #else false #endif ); this.m_long_prop[ACCOUNT_PROP_HEDGE_ALLOWED] = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<3245 ? false : ::AccountInfoInteger(ACCOUNT_HEDGE_ALLOWED) #else false #endif ); ... //--- Update the base object data and search for changes CBaseObjExt::Refresh(); } //+-------------------------------------------------------------------+
If MQL5 version is below 3245, there is no such property. Set false. If the terminal version is 3245 or higher, get the value from the new account property and set it to the array of object integer properties. In case of MQL4, always set false since it does not have such a property, as well as many other properties.
In the method updating all account data, set the value to the new object property in exactly the same way:
//+------------------------------------------------------------------+ //| Update all account data | //+------------------------------------------------------------------+ void CAccount::Refresh(void) { //--- Initialize event data this.m_is_event=false; this.m_hash_sum=0; ... this.m_long_prop[ACCOUNT_PROP_FIFO_CLOSE] = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<2155 ? false : ::AccountInfoInteger(ACCOUNT_FIFO_CLOSE) #else false #endif ); this.m_long_prop[ACCOUNT_PROP_HEDGE_ALLOWED] = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<3245 ? false : ::AccountInfoInteger(ACCOUNT_HEDGE_ALLOWED) #else false #endif ); ... CBaseObjExt::Refresh(); this.CheckEvents(); } //+------------------------------------------------------------------+
In the method creating the account object structure, add entering data to the two fields of the structure:
//+------------------------------------------------------------------+ //| Create the account object structure | //+------------------------------------------------------------------+ bool CAccount::ObjectToStruct(void) { //--- Save integer properties ... this.m_struct_obj.server_type=(int)this.ServerType(); this.m_struct_obj.fifo_close=this.FIFOClose(); this.m_struct_obj.hedge_allowed=this.HedgeAllowed(); ... //--- Save the structure to the uchar array ::ResetLastError(); if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY),(string)::GetLastError()); return false; } return true; } //+------------------------------------------------------------------+
Here we add a new property to the integer fields of the object structure, as well as set the FIFOClose account property added to the version 2155.
In the method creating an account object out of the structure, add setting the value from the structure field to the object property for a new property:
//+------------------------------------------------------------------+ //| Create the account object from the structure | //+------------------------------------------------------------------+ void CAccount::StructToObject(void) { //--- Save integer properties ... this.m_long_prop[ACCOUNT_PROP_FIFO_CLOSE] = this.m_struct_obj.fifo_close; this.m_long_prop[ACCOUNT_PROP_HEDGE_ALLOWED] = this.m_struct_obj.hedge_allowed; ... } //+------------------------------------------------------------------+
In the method returning the description of the account integer property, add the code block for displaying the new property description:
//+------------------------------------------------------------------+ //| Return the description of the account integer property | //+------------------------------------------------------------------+ string CAccount::GetPropertyDescription(ENUM_ACCOUNT_PROP_INTEGER property) { return ( ... property==ACCOUNT_PROP_FIFO_CLOSE ? CMessage::Text(MSG_ACC_PROP_FIFO_CLOSE)+": "+ (this.GetProperty(property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO)) : property==ACCOUNT_PROP_HEDGE_ALLOWED ? CMessage::Text(MSG_ACC_PROP_HEDGE_ALLOWED)+": "+ (this.GetProperty(property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO)) : "" ); } //+------------------------------------------------------------------+
Make similar improvements in the symbol object file in \MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh.
In the protected section of the class, declare the method returning the value of a new symbol property:
protected: //--- Protected parametric constructor CSymbol(ENUM_SYMBOL_STATUS symbol_status,const string name,const int index); //--- Get and return integer properties of a selected symbol from its parameters ... long SymbolCalcMode(void) const; long SymbolSwapMode(void) const; long SymbolSubscriptionDelay(void) const; long SymbolDigitsLot(void); int SymbolDigitsBySwap(void); ... //--- Search for a symbol and return the flag indicating its presence on the server bool Exist(void) const; public:
In the public section of the block of methods for a simplified access to symbol object properties, set the method returning the new property value:
//+------------------------------------------------------------------+ //| Methods for a simplified access to symbol object properties | //+------------------------------------------------------------------+ //--- Integer properties ... ENUM_SYMBOL_OPTION_MODE OptionMode(void) const { return (ENUM_SYMBOL_OPTION_MODE)this.GetProperty(SYMBOL_PROP_OPTION_MODE); } ENUM_SYMBOL_OPTION_RIGHT OptionRight(void) const { return (ENUM_SYMBOL_OPTION_RIGHT)this.GetProperty(SYMBOL_PROP_OPTION_RIGHT); } long SubscriptionDelay(void) const { return this.GetProperty(SYMBOL_PROP_SUBSCRIPTION_DELAY); } //--- Real properties
Here we simply use the GetProperty() method to return the value set in the array of the symbol object integer properties.
In the closed parametric constructor, set a new property to the array of object integer properties:
//+------------------------------------------------------------------+ //| Closed parametric constructor | //+------------------------------------------------------------------+ CSymbol::CSymbol(ENUM_SYMBOL_STATUS symbol_status,const string name,const int index) { //--- Save integer properties ... this.m_long_prop[SYMBOL_PROP_BOOKDEPTH_STATE] = this.m_book_subscribed; this.m_long_prop[SYMBOL_PROP_SUBSCRIPTION_DELAY] = this.SymbolSubscriptionDelay(); ... //--- Initializing default values of a trading object this.m_trade.Init(this.Name(),0,this.LotsMin(),5,0,0,false,this.GetCorrectTypeFilling(),this.GetCorrectTypeExpiration(),LOG_LEVEL_ERROR_MSG); } //+------------------------------------------------------------------+
The method returning the delay size for quotes passed by symbol in case of subscription-based symbols:
//+------------------------------------------------------------------+ //| Return the delay size for quotes passed by symbol | //| in case of subscription-based symbols | //+------------------------------------------------------------------+ long CSymbol::SymbolSubscriptionDelay(void) const { return ( #ifdef __MQL5__ (::TerminalInfoInteger(TERMINAL_BUILD)>=3245 ? ::SymbolInfoInteger(this.m_name,SYMBOL_SUBSCRIPTION_DELAY) : 0) #else 0 #endif ); } //+------------------------------------------------------------------+
Here, in case of MQL5, if the terminal version is 3245 or higher, return the value of a new symbol property, otherwise — return zero.
In case of MQL4, always return zero. It has no such property.
In the method returning the description of a symbol integer property, add a code block returning a new property description:
//+------------------------------------------------------------------+ //| Return the description of the symbol integer property | //+------------------------------------------------------------------+ string CSymbol::GetPropertyDescription(ENUM_SYMBOL_PROP_INTEGER property) { return ( ... property==SYMBOL_PROP_BACKGROUND_COLOR ? CMessage::Text(MSG_SYM_PROP_BACKGROUND_COLOR)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : #ifdef __MQL5__ (this.GetProperty(property)==CLR_MW_DEFAULT || this.GetProperty(property)==CLR_NONE ? ": ("+CMessage::Text(MSG_LIB_PROP_EMPTY)+")" : ": "+::ColorToString((color)this.GetProperty(property),true)) #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif ) : property==SYMBOL_PROP_SUBSCRIPTION_DELAY ? CMessage::Text(MSG_SYM_PROP_SUBSCRIPTION_DELAY)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : #ifdef __MQL5__ (::TerminalInfoInteger(TERMINAL_BUILD)<3245 ? ": ("+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3245)+")" : ": "+(string)this.GetProperty(property)) #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif ) : "" ); } //+------------------------------------------------------------------+
For color schemes of GUI elements in \MQL5\Include\DoEasy\GraphINI.mqh, add the text color value, increase the number of parameters in the color scheme from 4 to 5 and add the text color values to the color scheme value arrays:
//+------------------------------------------------------------------+ //| List of indices of color scheme parameters | //+------------------------------------------------------------------+ enum ENUM_COLOR_THEME_COLORS { COLOR_THEME_COLOR_FORM_BG, // Form background color COLOR_THEME_COLOR_FORM_FRAME, // Form frame color COLOR_THEME_COLOR_FORM_RECT_OUTER, // Form outline rectangle color COLOR_THEME_COLOR_FORM_SHADOW, // Form shadow color COLOR_THEME_COLOR_FORM_TEXT, // Form text color }; #define TOTAL_COLOR_THEME_COLORS (5) // Number of parameters in the color theme //+------------------------------------------------------------------+ //| The array containing color schemes | //+------------------------------------------------------------------+ color array_color_themes[TOTAL_COLOR_THEMES][TOTAL_COLOR_THEME_COLORS]= { //--- Parameters of the "Blue steel" color scheme { C'134,160,181', // Form background color C'134,160,181', // Form frame color clrDimGray, // Form outline rectangle color clrGray, // Form shadow color C'0x3E,0x3E,0x3E', // Form text color }, //--- Parameters of the "Light cyan gray" color scheme { C'181,196,196', // Form background color C'181,196,196', // Form frame color clrGray, // Form outline rectangle color clrGray, // Form shadow color C'0x3E,0x3E,0x3E', // Form text color }, }; //+------------------------------------------------------------------+
In the enumeration of frame styles, add the field indicating the frame absence:
//+------------------------------------------------------------------+ //| Frame styles | //+------------------------------------------------------------------+ enum ENUM_FRAME_STYLE { FRAME_STYLE_NONE, // No frame FRAME_STYLE_SIMPLE, // Simple frame FRAME_STYLE_FLAT, // Flat frame FRAME_STYLE_BEVEL, // Embossed (convex) FRAME_STYLE_STAMP, // Embossed (concave) }; //+------------------------------------------------------------------+
Improve the class of the basic object of all library graphical objects in \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh.
We need to be able to set 0 or NULL in custom programs when specifying the current chart ID, as well as instead of specifying a numeric ID value or passing the ChartID() function, add verification of the value passed to the SetChartID() method:
public: //--- Return the prefix name string NamePrefix(void) const { return this.m_name_prefix; } //--- Set the values of the class variables void SetObjectID(const long value) { this.m_object_id=value; } void SetBelong(const ENUM_GRAPH_OBJ_BELONG belong){ this.m_belong=belong; } void SetTypeGraphObject(const ENUM_OBJECT obj) { this.m_type_graph_obj=obj; } void SetTypeElement(const ENUM_GRAPH_ELEMENT_TYPE type) { this.m_type_element=type; } void SetSpecies(const ENUM_GRAPH_OBJ_SPECIES species){ this.m_species=species; } void SetGroup(const int group) { this.m_group=group; } void SetName(const string name) { this.m_name=name; } void SetDigits(const int value) { this.m_digits=value; } void SetChartID(const long chart_id) { this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id); } //--- Set the "Background object" flag
Here we check, which value has been passed to the method. If it is 0 or NULL, assign the current chart ID to the variable. Otherwise, assign the value passed to the method.
In the method returning the graphical element type description, add returning the Panel object description:
//+------------------------------------------------------------------+ //| Return the description of the graphical element type | //+------------------------------------------------------------------+ string CGBaseObj::TypeElementDescription(void) { return ( this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_ELEMENT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_SHADOW_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WINDOW ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW) : //--- this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_PANEL) : "Unknown" ); } //+------------------------------------------------------------------+
In the class of the graphical element object in \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, namely in the object structure, add a new field for the element availability property:
//+------------------------------------------------------------------+ //| Class of the graphical element object | //+------------------------------------------------------------------+ class CGCnvElement : public CGBaseObj { protected: CCanvas m_canvas; // CCanvas class object CPause m_pause; // Pause class object bool m_shadow; // Shadow presence color m_chart_color_bg; // Chart background color uint m_duplicate_res[]; // Array for storing resource data copy //--- Create (1) the object structure and (2) the object from the structure virtual bool ObjectToStruct(void); virtual void StructToObject(void); private: struct SData { //--- Object integer properties ... int coord_act_bottom; // Bottom border of the element active area long zorder; // Priority of a graphical object for receiving the event of clicking on a chart bool enabled; // Element availability flag //--- Object real properties //--- Object string properties uchar name_obj[64]; // Graphical element object name uchar name_res[64]; // Graphical resource name }; SData m_struct_obj; // Object structure uchar m_uchar_array[]; // uchar array of the object structure
Add new methods for setting and returning the element availability value in the block of methods for a simplified access to the object properties:
//+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ ... //--- Set (1) object movability, (2) activity, (3) interaction, //--- (4) element ID, (5) element index in the list, (6) availability and (7) shadow flag void SetMovable(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,flag); } void SetActive(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,flag); } void SetInteraction(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,flag); } void SetID(const int id) { this.SetProperty(CANV_ELEMENT_PROP_ID,id); } void SetNumber(const int number) { this.SetProperty(CANV_ELEMENT_PROP_NUM,number); } void SetEnabled(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_ENABLED,flag); } void SetShadow(const bool flag) { this.m_shadow=flag; } ... //--- Return the (1) element movability, (2) activity, (3) interaction and (4) availability flag bool Movable(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE); } bool Active(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE); } bool Interaction(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_INTERACTION); } bool Enabled(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_ENABLED); } //--- Return (1) the object name, (2) the graphical resource name, (3) the chart ID and (4) the chart subwindow index
In one of the canvas clearing methods, remove the default flag values:
//+------------------------------------------------------------------+ //| The methods of filling, clearing and updating raster data | //+------------------------------------------------------------------+ //--- Clear the element filling it with color and opacity void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Clear the element completely void Erase(const bool redraw=false); //--- Update the element void Update(const bool redraw=false) { this.m_canvas.Update(redraw); }
previously, the method looked as follows:
void Erase(color &colors[],const uchar opacity,const bool vgradient=true,const bool cycle=false,const bool redraw=false);
and it was impossible to use since the compiler was unable to select a correct overloaded method.
In the parametric constructor, add the verification of a passed chart ID value and setting the element availability flag, as well as default font values:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int element_id, const int element_num, const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable=true, const bool activity=true, const bool redraw=false) : m_shadow(false) { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND); this.m_name=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id); this.m_subwindow=wnd_num; this.m_type_element=element_type; this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.m_text_anchor=0; this.m_text_x=0; this.m_text_y=0; this.m_color_bg=colour; this.m_opacity=opacity; if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw)) { ... this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,activity); // Element activity flag this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,false); // Flag of interaction with the outside environment this.SetProperty(CANV_ELEMENT_PROP_ENABLED,true); // Element availability flag this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge()); // Element right border ... } //+------------------------------------------------------------------+
Do the same in the protected constructor:
//+------------------------------------------------------------------+ //| Protected constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h) : m_shadow(false) { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND); this.m_name=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id); this.m_subwindow=wnd_num; this.m_type_element=element_type; this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.m_text_anchor=0; this.m_text_x=0; this.m_text_y=0; this.m_color_bg=CLR_CANV_NULL; this.m_opacity=0; if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,this.m_color_bg,this.m_opacity,false)) { ... this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,false); // Element activity flag this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,false); // Flag of interaction with the outside environment this.SetProperty(CANV_ELEMENT_PROP_ENABLED,true); // Element availability flag this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge()); // Element right border ... } //+------------------------------------------------------------------+
In the method creating an object structure, add filling a new structure field with the new element availability flag:
//+------------------------------------------------------------------+ //| Create the object structure | //+------------------------------------------------------------------+ bool CGCnvElement::ObjectToStruct(void) { //--- Save integer properties ... this.m_struct_obj.active=(bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE); // Element activity flag this.m_struct_obj.interaction=(bool)this.GetProperty(CANV_ELEMENT_PROP_INTERACTION); // Flag of interaction with the outside environment this.m_struct_obj.enabled=(bool)this.GetProperty(CANV_ELEMENT_PROP_ENABLED); // Element availability flag this.m_struct_obj.coord_act_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_X); // X coordinate of the element active area this.m_struct_obj.coord_act_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y); // Y coordinate of the element active area ... return true; } //+------------------------------------------------------------------+
In the method creating an object out of the structure, add the entry to the value object availability property from the appropriate structure field:
//+------------------------------------------------------------------+ //| Create the object from the structure | //+------------------------------------------------------------------+ void CGCnvElement::StructToObject(void) { //--- Save integer properties ... this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,this.m_struct_obj.active); // Element activity flag this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,this.m_struct_obj.interaction); // Flag of interaction with the outside environment this.SetProperty(CANV_ELEMENT_PROP_ENABLED,this.m_struct_obj.enabled); // Element availability flag this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.m_struct_obj.coord_act_x); // X coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.m_struct_obj.coord_act_y); // Y coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.m_struct_obj.coord_act_right); // Right border of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.m_struct_obj.coord_act_bottom); // Bottom border of the element active area this.m_color_bg=this.m_struct_obj.color_bg; // Element background color this.m_opacity=this.m_struct_obj.opacity; // Element opacity this.m_zorder=this.m_struct_obj.zorder; // Priority of a graphical object for receiving the on-chart mouse click event ... //--- Save string properties this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj));// Graphical element object name this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res));// Graphical resource name } //+------------------------------------------------------------------+
In the method creating a graphical element object, add verification of a passed chart ID value as well:
//+------------------------------------------------------------------+ //| Create the graphical element object | //+------------------------------------------------------------------+ bool CGCnvElement::Create(const long chart_id, // Chart ID const int wnd_num, // Chart subwindow const string name, // Element name const int x, // X coordinate const int y, // Y coordinate const int w, // Width const int h, // Height const color colour, // Background color const uchar opacity, // Opacity const bool redraw=false) // Flag indicating the need to redraw { ::ResetLastError(); if(this.m_canvas.CreateBitmapLabel((chart_id==NULL ? ::ChartID() : chart_id),wnd_num,name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE)) { this.Erase(CLR_CANV_NULL); this.m_canvas.Update(redraw); this.m_shift_y=(int)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_WINDOW_YDISTANCE,wnd_num); return true; } CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //+------------------------------------------------------------------+
Let's improve the form object class in \MQL5\Include\DoEasy\Objects\Graph\Form.mqh.
Private variable initialization method
//+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CForm : public CGCnvElement { private: CArrayObj m_list_elements; // List of attached elements CAnimations *m_animations; // Pointer to the animation object CShadowObj *m_shadow_obj; // Pointer to the shadow object CMouseState m_mouse; // "Mouse status" class object ENUM_MOUSE_FORM_STATE m_mouse_form_state; // Mouse status relative to the form ushort m_mouse_state_flags; // Mouse status flags color m_color_frame; // Form frame color int m_frame_width_left; // Form frame width to the left int m_frame_width_right; // Form frame width to the right int m_frame_width_top; // Form frame width at the top int m_frame_width_bottom; // Form frame width at the bottom int m_offset_x; // Offset of the X coordinate relative to the cursor int m_offset_y; // Offset of the Y coordinate relative to the cursor //--- Initialize the variables void Initialize(void); //--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames void ResetArrayFrameT(void); void ResetArrayFrameQ(void); void ResetArrayFrameG(void);
move to the protected class section since the method will be needed in descendant objects, and declare a new method for deinitializing the class object:
//--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames void ResetArrayFrameT(void); void ResetArrayFrameQ(void); void ResetArrayFrameG(void); //--- Return the name of the dependent object string CreateNameDependentObject(const string base_name) const { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name; } //--- Create a new graphical object CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); //--- Create a shadow object void CreateShadowObj(const color colour,const uchar opacity); protected: //--- Initialize the variables void Initialize(void); void Deinitialize(void); public: //--- Return (1) the mouse status relative to the form, as well as (2) X and (3) Y coordinate of the cursor
Rename the GetList() method returning the list of attached objects to GetListElements(), which is more suitable in terms of its function:
//--- Return (1) the list of attached objects and (2) the shadow object CForm *GetObject(void) { return &this; } CArrayObj *GetListElements(void) { return &this.m_list_elements; } CGCnvElement *GetShadowObj(void) { return this.m_shadow_obj; } //--- Return the pointer to (1) the animation object, the list of (2) text and (3) rectangular animation frames
In the public section of the class, declare the method adding a new attached element to the list of attached form elements:
//--- Create a new attached element bool CreateNewElement(const int element_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); //--- Add a new attached element bool AddNewElement(CGCnvElement *obj,const int x,const int y); //--- Draw an object shadow void DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=4);
Move the code block, which removes all used dynamic class objects, from the class destructor
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CForm::~CForm() { if(this.m_shadow_obj!=NULL) delete this.m_shadow_obj; if(this.m_animations!=NULL) delete this.m_animations; } //+------------------------------------------------------------------+
to the new deinitialization method:
//+------------------------------------------------------------------+ //| Deinitialize the variables | //+------------------------------------------------------------------+ void CForm::Deinitialize(void) { if(this.m_shadow_obj!=NULL) delete this.m_shadow_obj; if(this.m_animations!=NULL) delete this.m_animations; } //+------------------------------------------------------------------+
Call the following method in the destructor:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CForm::~CForm() { this.Deinitialize(); } //+------------------------------------------------------------------+
This enables us to remove unnecessary parent class dynamic objects from inherited classes.
The method adding a new attached element to the list of attached object elements:
//+------------------------------------------------------------------+ //| Add a new attached element | //+------------------------------------------------------------------+ bool CForm::AddNewElement(CGCnvElement *obj,const int x,const int y) { if(obj==NULL) return false; this.m_list_elements.Sort(SORT_BY_CANV_ELEMENT_NAME_OBJ); int index=this.m_list_elements.Search(obj); if(index>WRONG_VALUE) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_OBJ_ALREADY_IN_LIST),": ",obj.NameObj()); return false; } if(!this.m_list_elements.Add(obj)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST),": ",obj.NameObj()); return false; } return true; } //+------------------------------------------------------------------+
The method receives the pointer to the object to be added to the list of attached objects.
Sort the list of elements by a name of a specified object and search for such an object in the list.
If no object with the same name is already present in the list, inform of that and return false.
If failed to place an object to the list of attached objects, inform of that and return false.
As a result, return true.
The method creating a new attached element now calls the method adding the created object to the list:
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CForm::CreateNewElement(const int element_num, const string element_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { CGCnvElement *obj=this.CreateNewGObject(GRAPH_ELEMENT_TYPE_ELEMENT,element_num,element_name,x,y,w,h,colour,opacity,movable,activity); if(obj==NULL) return false; if(!this.AddNewElement(obj,x,y)) { delete obj; return false; } return true; } //+------------------------------------------------------------------+
Previously, I added the newly created object to the list in this method. This was irrational since we are able to add graphical elements from other program parts to the list of attached objects (not only when creating an object).
In the method creating a shadow object, the moveability flag was set to true. This made the shadow object moveable. I believe, this behavior is incorrect. Instead, the property value should be inherited from the object the shadow object is built for. Let's fix this:
//+------------------------------------------------------------------+ //| Create the shadow object | //+------------------------------------------------------------------+ void CForm::CreateShadowObj(const color colour,const uchar opacity) { //--- If the shadow flag is disabled or the shadow object already exists, exit if(!this.m_shadow || this.m_shadow_obj!=NULL) return; //--- Calculate the shadow object coordinates according to the offset from the top and left int x=this.CoordX()-OUTER_AREA_SIZE; int y=this.CoordY()-OUTER_AREA_SIZE; //--- Calculate the width and height in accordance with the top, bottom, left and right offsets int w=this.Width()+OUTER_AREA_SIZE*2; int h=this.Height()+OUTER_AREA_SIZE*2; //--- Create a new shadow object and set the pointer to it in the variable this.m_shadow_obj=new CShadowObj(this.ChartID(),this.SubWindow(),this.CreateNameDependentObject("Shadow"),x,y,w,h); if(this.m_shadow_obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ)); return; } //--- Set the properties for the created shadow object this.m_shadow_obj.SetID(this.ID()); this.m_shadow_obj.SetNumber(-1); this.m_shadow_obj.SetOpacityShadow(opacity); this.m_shadow_obj.SetColorShadow(colour); this.m_shadow_obj.SetMovable(this.Movable()); this.m_shadow_obj.SetActive(false); this.m_shadow_obj.SetVisible(false,false); //--- Move the form object to the foreground this.BringToTop(); } //+------------------------------------------------------------------+
All preparatory stages are complete.
WinForms Panel object class
The panel object will be derived from the form object class. In other words, it will feature the entire form functionality and properties. Besides, I will add new properties and functionality to it. The panel will be able to place other objects in it, as well as change its size to fit the content and enable the auto scroll if the content goes beyond the panel.
In the current article, I will only make a preparation of the panel object - I will define all its properties and create methods for setting and returning them. In subsequent articles, I will gradually add all the functionality of the panel object. Here I will only be able to create a panel object using its constructor.
For all WinForms management elements, define a new library directory.
Create a new folder \MQL5\Include\DoEasy\Objects\Graph\WForms\ with subfolders named after MS Visual Studio control groups in the amount defined at the beginning of the article:
- \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\
- \MQL5\Include\DoEasy\Objects\Graph\WForms\Components\
- \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\
- \MQL5\Include\DoEasy\Objects\Graph\WForms\Data\
- \MQL5\Include\DoEasy\Objects\Graph\WForms\Dialogs\
- \MQL5\Include\DoEasy\Objects\Graph\WForms\Menu & Toolbars\
- \MQL5\Include\DoEasy\Objects\Graph\WForms\Printing
Since the panel is a container for other objects, the object class file is located in the appropriate folder \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\.
In the specified file, create a new file Panel.mqh of the CPanel derived from CForm whose file should be included:
//+------------------------------------------------------------------+ //| Panel.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Form.mqh" //+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+ class CPanel : public CForm { }
In the private section of the class, declare all the necessary variables and arrays:
//+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+ class CPanel : public CForm { private: color m_fore_color; // Default text color for all panel objects ENUM_FRAME_STYLE m_border_style; // Panel frame style bool m_autoscroll; // Auto scrollbar flag int m_autoscroll_margin[2]; // Array of fields around the control during an auto scroll bool m_autosize; // Flag of the element auto resizing depending on the content ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode; // Mode of the element auto resizing depending on the content ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode; // Mode of binding element borders to the container int m_margin[4]; // Array of gaps of all sides between the fields of the current and adjacent controls int m_padding[4]; // Array of gaps of all sides inside controls public:
To grasp the terms Margin, Padding and AutoSize, let's consider the following example from MS Windows Forms .NET Framework 4.X help:
... Three of the most important are the Margin, Padding, and AutoSize properties, which are present on all Windows Forms controls.
The Margin property defines the space around the control that keeps other controls a specified distance from the control's borders.
The Padding property defines the space in the interior of a control that keeps the control's content (for example, the value of its Text property) a specified distance from the control's borders.
The AutoSize property tells a control to automatically size itself to its contents. It will not resize itself to be smaller than the value of its original Size property, and it will account for the value of its Padding property.
In the public section of the class, write the methods for setting and returning the values of all declared class variables:
public: //--- (1) Set and (2) return the default text color of all panel objects void ForeColor(const color clr) { this.m_fore_color=clr; } color ForeColor(void) const { return this.m_fore_color; } //--- (1) Set and (2) return the frame style void BorderStyle(const ENUM_FRAME_STYLE style) { this.m_border_style=style; } ENUM_FRAME_STYLE BorderStyle(void) const { return this.m_border_style; } //--- (1) Set and (2) return the auto scrollbar flag void AutoScroll(const bool flag) { this.m_autoscroll=flag; } bool AutoScroll(void) { return this.m_autoscroll; } //--- Set the (1) field width, (2) height, (3) the height of all fields around the control during auto scrolling void AutoScrollMarginWidth(const int value) { this.m_autoscroll_margin[0]=value; } void AutoScrollMarginHeight(const int value) { this.m_autoscroll_margin[1]=value; } void AutoScrollMarginAll(const int value) { this.AutoScrollMarginWidth(value); this.AutoScrollMarginHeight(value); } //--- Return the (1) field width and (2) height around the control during auto scrolling int AutoScrollMarginWidth(void) const { return this.m_autoscroll_margin[0]; } int AutoScrollMarginHeight(void) const { return this.m_autoscroll_margin[1]; } //--- (1) Set and (2) return the flag of the element auto resizing depending on the content void AutoSize(const bool flag) { this.m_autosize=flag; } bool AutoSize(void) { return this.m_autosize; } //--- (1) Set and (2) return the mode of the element auto resizing depending on the content void AutoSizeMode(const ENUM_CANV_ELEMENT_AUTO_SIZE_MODE mode) { this.m_autosize_mode=mode; } ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void) const { return this.m_autosize_mode; } //--- (1) Set and (2) return the mode of binding element borders to the container void DockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode){ this.m_dock_mode=mode; } ENUM_CANV_ELEMENT_DOCK_MODE DockMode(void) const { return this.m_dock_mode; } //--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides between the fields of this and another control void MarginLeft(const int value) { this.m_margin[0]=value; } void MarginTop(const int value) { this.m_margin[1]=value; } void MarginRight(const int value) { this.m_margin[2]=value; } void MarginBottom(const int value) { this.m_margin[3]=value; } void MarginAll(const int value) { this.MarginLeft(value); this.MarginTop(value); this.MarginRight(value); this.MarginBottom(value); } //--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields of this and another control int MarginLeft(void) const { return this.m_margin[0]; } int MarginTop(void) const { return this.m_margin[1]; } int MarginRight(void) const { return this.m_margin[2]; } int MarginBottom(void) const { return this.m_margin[3]; } //--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control void PaddingLeft(const int value) { this.m_padding[0]=value; } void PaddingTop(const int value) { this.m_padding[1]=value; } void PaddingRight(const int value) { this.m_padding[2]=value; } void PaddingBottom(const int value) { this.m_padding[3]=value; } void PaddingAll(const int value) { this.PaddingLeft(value); this.PaddingTop(value); this.PaddingRight(value); this.PaddingBottom(value); } //--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields inside the control int PaddingLeft(void) const { return this.m_padding[0]; } int PaddingTop(void) const { return this.m_padding[1]; } int PaddingRight(void) const { return this.m_padding[2]; } int PaddingBottom(void) const { return this.m_padding[3]; } //--- Constructors CPanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); CPanel(const int subwindow, const string name, const int x, const int y, const int w, const int h); CPanel(const string name, const int x, const int y, const int w, const int h); CPanel(const string name) : CForm(::ChartID(),0,name,0,0,0,0) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_FORE_COLOR; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //--- Destructor ~CPanel(); }; //+------------------------------------------------------------------+
For some of them, it is possible to simultaneously set each property corresponding to each side of the object.
For example, for the Margin value in MS Visual Studio, it is possible to set both each property separately, and all four at the same time:
We have four class constructors: specifying (1) the chart ID, chart subwindow, object name and coordinates with size, (2) current chart subwindow, object name and coordinates with size, (3) object name and coordinates with size, (4) object name with zero coordinates and size:
//+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CPanel::CPanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CForm(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_FORE_COLOR; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //+------------------------------------------------------------------+ //| Current chart constructor specifying the subwindow | //+------------------------------------------------------------------+ CPanel::CPanel(const int subwindow, const string name, const int x, const int y, const int w, const int h) : CForm(::ChartID(),subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_FORE_COLOR; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //+------------------------------------------------------------------+ //| Constructor on the current chart in the main chart window | //+------------------------------------------------------------------+ CPanel::CPanel(const string name, const int x, const int y, const int w, const int h) : CForm(::ChartID(),0,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_FORE_COLOR; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //+------------------------------------------------------------------+
In the initialization string of each constructor, pass the necessary parameters to the parent class constructor.
Next, in the constructor body, set the graphical element type, library object type, the default panel text color, set Margin for all sides equal to 3, Padding equal to 0 and initialize the parent class variables.
This is sufficient for a simple creation of a panel object on a terminal chart. All other things for the Panel object will be implemented in the coming articles.
In the class destructor, call the parent class deinitialization method:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CPanel::~CPanel() { CForm::Deinitialize(); } //+------------------------------------------------------------------+
Now we need to improve the collection class of graphical elements \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh.
Instead of the form object file, include the panel object file:
//+------------------------------------------------------------------+ //| GraphElementsCollection.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Services\Select.mqh" #include "..\Objects\Graph\WForms\Containers\Panel.mqh" #include "..\Objects\Graph\Standard\GStdVLineObj.mqh"
Since the panel object is derived from the form object, all objects of its parent hierarchy will be visible in the collection class.
In the public section of the class, write two methods returning the list of graphical elements by chart and object IDs, as well as by chart ID and object name:
//--- Return the list of graphical objects by chart ID and group CArrayObj *GetListStdGraphObjByGroup(const long chart_id,const int group) { CArrayObj *list=GetList(GRAPH_OBJ_PROP_CHART_ID,0,chart_id,EQUAL); return CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_GROUP,0,group,EQUAL); } //--- Return the list of graphical elements by chart and object IDs CArrayObj *GetListCanvElementByID(const long chart_id,const int element_id) { CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL); return CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL);; } //--- Return the list of graphical elements by chart ID and object name CArrayObj *GetListCanvElementByName(const long chart_id,const string name) { CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL); return CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,name,EQUAL);; } //--- Constructor
I have repeatedly considered the logic of such methods earlier. Here we simply sort the list by the necessary parameters and return the resulting list, which is to feature the pointer to the object found in the collection list.
If no object is found, the methods will return NULL.
At the very end of the class body, write the methods for creating graphical element, form and panel objects:
//--- Create a graphical element object on canvas on a specified chart and subwindow int CreateElement(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, const color clr, const uchar opacity, const bool movable, const bool activity, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,activity,redraw); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.Erase(clr,opacity,redraw); return obj.ID(); } //--- Create a graphical element object on canvas on a specified chart and subwindow with the vertical gradient filling int CreateElementVGradient(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr[0],opacity,movable,activity,redraw); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.Erase(clr,opacity,true,false,redraw); return obj.ID(); } //--- Create a graphical element object on canvas on a specified chart and subwindow with the horizontal gradient filling int CreateElementHGradient(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr[0],opacity,movable,activity,redraw); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.Erase(clr,opacity,false,false,redraw); return obj.ID(); } //--- Create a graphical element object on canvas on a specified chart and subwindow with the cyclic vertical gradient filling int CreateElementVGradientCicle(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr[0],opacity,movable,activity,redraw); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.Erase(clr,opacity,true,true,redraw); return obj.ID(); } //--- Create a graphical element object on canvas on a specified chart and subwindow with the cyclic horizontal gradient filling int CreateElementHGradientCicle(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr[0],opacity,movable,activity,redraw); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.Erase(clr,opacity,false,true,redraw); return obj.ID(); } //--- Create a graphical object form object on canvas on a specified chart and subwindow int CreateForm(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, const color clr, const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr); obj.SetColorFrame(clr); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,redraw); return obj.ID(); } //--- Create a graphical object form object on canvas on a specified chart and subwindow with the vertical gradient filling int CreateFormVGradient(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr[0]); obj.SetColorFrame(clr[0]); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,true,false,redraw); return obj.ID(); } //--- Create a graphical object form object on canvas on a specified chart and subwindow with the horizontal gradient filling int CreateFormHGradient(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr[0]); obj.SetColorFrame(clr[0]); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,false,false,redraw); return obj.ID(); } //--- Create a graphical object form object on canvas on a specified chart and subwindow with the cyclic vertical gradient filling int CreateFormVGradientCicle(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr[0]); obj.SetColorFrame(clr[0]); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,true,true,redraw); return obj.ID(); } //--- Create a graphical object form object on canvas on a specified chart and subwindow with the cyclic horizontal gradient filling int CreateFormHGradientCicle(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr[0]); obj.SetColorFrame(clr[0]); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,false,true,redraw); return obj.ID(); } //--- Create graphical object WinForms Panel object on canvas on a specified chart and subwindow int CreatePanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, const color clr, const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CPanel *obj=new CPanel(chart_id,subwindow,name,x,y,w,h); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr); obj.SetColorFrame(clr); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,redraw); return obj.ID(); } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+
The methods for creating elements and forms are almost identical. The only difference is in the method of filling the background with color. It is either a single permanent color or filling with gradient. The gradient filling has several types: vertical, horizontal and cyclic vertical and horizontal. Right after creating an object, it is added to the collection list of graphical elements and the minimum required properties (passed to the method when it is called) are set for it.
In the method resetting interaction flags for all forms except the specified one, change the object type to the element, since graphical elements are the minimum object for building GUI elements:
//+--------------------------------------------------------------------+ //| Reset all interaction flags for all forms except the specified one | //+--------------------------------------------------------------------+ void CGraphElementsCollection::ResetAllInteractionExeptOne(CGCnvElement *form_exept) { //--- In the loop by all graphical element collection class objects int total=this.m_list_all_canv_elm_obj.Total(); for(int i=0;i<total;i++) { //--- get the pointer to the object CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(i); //--- if failed to receive the pointer, or it is not a form, or it is not a form whose pointer has been passed to the method, move on if(obj==NULL || obj.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_FORM || (obj.Name()==form_exept.Name() && obj.ChartID()==form_exept.ChartID())) continue; //--- Reset the interaction flag for the current form in the loop obj.SetInteraction(false); } } //+------------------------------------------------------------------+
Since we now have the methods for creating graphical elements, forms and panels, the method adding a graphical element to the collection list in \MQL5\Include\DoEasy\Engine.mqh of the main CEngine library object is no longer needed. So let's delete it:
//--- Return the list of graphical elements on canvas CArrayObj *GetListCanvElement(void) { return this.m_graph_objects.GetListCanvElm(); } //--- Add the graphical element on canvas to the collection bool GraphAddCanvElmToCollection(CGCnvElement *element) { return this.m_graph_objects.AddCanvElmToCollection(element); } //--- Fill in the array with IDs of the charts opened in the terminal void GraphGetArrayChartsID(long &array_charts_id[])
It is replaced with methods returning the list of graphical elements by chart and object IDs and the list of graphical elements by chart ID and object name:
//--- Return the list of graphical elements on canvas CArrayObj *GetListCanvElement(void) { return this.m_graph_objects.GetListCanvElm(); } //--- Return the list of graphical elements by chart and object IDs CArrayObj *GetListCanvElementByID(const long chart_id,const int element_id) { return this.m_graph_objects.GetListCanvElementByID(chart_id,element_id); } //--- Return the list of graphical elements by chart ID and object name CArrayObj *GetListCanvElementByName(const long chart_id,const string name) { return this.m_graph_objects.GetListCanvElementByName(chart_id,name); } //--- Fill in the array with IDs of the charts opened in the terminal void GraphGetArrayChartsID(long &array_charts_id[])
These two methods simply return the result of the request from the same-name methods of the collection class of graphical elements we have considered above.
Now we are all set for the test.
Test
To perform the test, let's use the EA from the previous article and save it to \MQL5\Experts\TestDoEasy\Part101\ as TestDoEasyPart101.mq5.
I will leave creation of three form objects from the previous article and add three element objects and one panel object.
On each of the objects, display the text with its name and ID in the graphical element collection.
In the OnInit() handler, add creation of all objects using the methods added to the method collection class in the current article:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set EA global variables ArrayResize(array_clr,2); // Array of gradient filling colors array_clr[0]=C'26,100,128'; // Original ≈Dark-azure color array_clr[1]=C'35,133,169'; // Lightened original color //--- Create the array with the current symbol and set it to be used in the library string array[1]={Symbol()}; engine.SetUsedSymbols(array); //--- Create the timeseries object for the current symbol and period, and show its description in the journal engine.SeriesCreate(Symbol(),Period()); engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions //--- Create form objects int obj_id=WRONG_VALUE; CArrayObj *list=NULL; CForm *form=NULL; for(int i=0;i<FORMS_TOTAL;i++) { obj_id=engine.GetGraphicObjCollection().CreateFormVGradient(ChartID(),0,"Form_0"+string(i+1),30,(form==NULL ? 100 : form.BottomEdge()+20),100,30,array_clr,245,true,true); list=engine.GetListCanvElementByID(ChartID(),obj_id); form=list.At(0); if(form==NULL) continue; //--- Set ZOrder to zero, display the text describing the gradient type and update the form //--- Text parameters: the text coordinates and the anchor point in the form center //--- Create a new text animation frame with the ID of 0 and display the text on the form form.SetZorder(0,false); form.TextOnBG(0,"Form: ID "+(string)form.ID()+", ZOrder "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,false); } //--- Create four graphical elements CGCnvElement *elm=NULL; array_clr[0]=C'0x65,0xA4,0xA9'; array_clr[1]=C'0x48,0x75,0xA2'; //--- Vertical gradient obj_id=engine.GetGraphicObjCollection().CreateElementVGradient(NULL,0,"CElmVG",form.RightEdge()+50,20,200,50,array_clr,127,true,true,true); list=engine.GetGraphicObjCollection().GetListCanvElementByID(ChartID(),obj_id); elm=list.At(0); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,"Element: ID "+(string)elm.ID(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Vertical cyclic gradient obj_id=engine.GetGraphicObjCollection().CreateElementVGradientCicle(NULL,0,"CElmVGC",form.RightEdge()+50,80, 200,50,array_clr,127,true,true,true); list=engine.GetGraphicObjCollection().GetListCanvElementByID(ChartID(),obj_id); elm=list.At(0); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,"Element: ID "+(string)elm.ID(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Horizontal gradient obj_id=engine.GetGraphicObjCollection().CreateElementHGradient(NULL,0,"CElmHG",form.RightEdge()+50,140,200,50,array_clr,127,true,true,true); list=engine.GetGraphicObjCollection().GetListCanvElementByID(ChartID(),obj_id); elm=list.At(0); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,"Element: ID "+(string)elm.ID(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Horizontal cyclic gradient obj_id=engine.GetGraphicObjCollection().CreateElementHGradientCicle(NULL,0,"CElmHGC",form.RightEdge()+50,200,200,50,array_clr,127,true,true,false); list=engine.GetGraphicObjCollection().GetListCanvElementByID(ChartID(),obj_id); elm=list.At(0); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,"Element: ID "+(string)elm.ID(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Create WinForms Panel object CPanel *pnl=NULL; obj_id=engine.GetGraphicObjCollection().CreatePanel(ChartID(),0,"WFPanel",elm.RightEdge()+50,50,150,150,array_clr[0],200,true,true,false,true); list=engine.GetListCanvElementByID(ChartID(),obj_id); pnl=list.At(0); if(pnl!=NULL) { pnl.SetFontSize(10); pnl.TextOnBG(0,"WinForm Panel: ID "+(string)pnl.ID(),4,2,FRAME_ANCHOR_LEFT_TOP,pnl.ForeColor(),pnl.Opacity()); pnl.Update(true); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Shape objects are filled with a vertical gradient, while each element object is filled with its own type of gradient. The panel object is filled with one color.
Compile the EA and launch it on the chart:
Forms react to mouse movement and are always placed on top of graphical objects added to the chart. Gradient fillings of element objects are drawn correctly and there is only one panel object color. However, neither the elements, nor the panel react to the mouse and are located in the background under all graphical objects. This happens because I handled mouse events for form objects only. The fact that the panel is essentially a form does not matter since I explicitly handle the CForm class only. I will fix all this later.
What's next?
In the next article, I will continue the development of the WinForms Panel object class.
*The last article of the previous series:
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/10663
- 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 congratulate you on all your hard work...this was quite the project.
I am just starting on this and am trying to compile the TestDoEasyPart101.mq5 ... I am getting the following 4 errors:
1) CTrading::OpenPosition<...... (cannot access private member function)
all the other errors are basically the same, so I must having a path problem but can't narrow it down.
I copied the DoEasy Files for include so the heirarchy is as follows:
MQL5\Include\DoEasy\all the files that were in your DoEasy folder under the include
I did the following at the top of the TradingControl.mqh file: #include <DoEasy\trading.mqh>
what might be my problem...as I said it appears you did at least 100 articles to build what you have at the start of this...any suggestions on how I get this to compile?
thank you
Dean Fredrickson
I congratulate you on all your hard work...this was quite the project .
I am just starting on this and am trying to compile the TestDoEasyPart101.mq5 ... I am getting the following 4 errors:
1) CTrading::OpenPosition<...... (cannot access private member function)
all the other errors are basically the same, so I must having a path problem but can't narrow it down.
I copied the DoEasy Files for include so the heirarchy is as follows:
MQL5\Include\DoEasy\all the files that were in your DoEasy folder under the include
I did the following at the top of the TradingControl.mqh file: #include <DoEasy\trading.mqh>
what might be my problem...as I said it appears you did at least 100 articles to build what you have at the start of this...any suggestions on how I get this to compile?
thank you
Dean Fredrickson
In the Trading.mqh file, make the following changes:
This will allow methods to be visible from derived classes.
The error was introduced by me due to inattention, but the old compiler missed it. After updating the terminal, the compiler saw this error.