DoEasy. Controls (Part 11): WinForms objects — groups, CheckedListBox WinForms object
Contents
Concept
WinForms objects bound to a single container become a set of objects combined into a single group. Regardless of whether they are bound to the GroupBox object or to a panel, the container becomes an entity combining all objects into one group. The objects begin to behave according to certain rules of this group. For example, the RadioButton object is practically of no use when it is alone. If it is created unselected, its checkbox is activated after clicking on it and it is impossible to uncheck it again. To uncheck the box, we need to select another object of the same type. Here lies the difference. If we select another RadioButton object within the same container, the checkbox is disabled on the first one and is activated for the second one, which was clicked. If we attempt to select a RadioButton bound to another container, the checkbox of the first selected object in the first group is not disabled, which is expected since we are dealing with different object groups in different containers.
But what if we want to have two sets of RadioButton objects in the same container that work independently of each other? After all, they are combined into one set of objects by their container (from which they should inherit the index of its group) and work according to the common group index received from their container. In order to make several independently working sets of such objects in one container, I will introduce the concept of object groups.
If we create a set of six RadioButton objects in the container with the group index of 1, the group index of 1 is assigned to all of them. They will all be linked together by the group index of their container and work accordingly. Clicking on any of the six RadioButton objects will deselect the rest five.
But if we assign the group 2 to four objects of the group 1, while the remaining two objects form the group 3, two object subgroups 2 and 3 are created within the container with the group 1. Accordingly, each of these new groups will work only in conjunction with the objects of its group.
This will allow us to create various subgroups in one container, joined into their own group under its own index, and there will be no need to create new containers for groups inside the main one.
Thus, by combining two toggle buttons into one group, we will turn them into a two-button switch, in which pressing the first button releases the second, and vice versa. Thus, it will be possible to make it so that several Toggle buttons combined into one group can be either in the state when all buttons are not pressed, or only one of them is in the pressed state, while the rest are released.
Besides, I will improve the account object class since the terminal name may differ from the standard one on some servers. Usually, when requesting the name of the terminal, the server returns the "MetaTrader 5" string, but brokers sometimes add something else to the string changing the terminal name. Therefore, it would be reasonable to look for the "MetaTrader 5" substring in the terminal name instead to define the server type.
Improving library classes
In \MQL5\Include\DoEasy\Defines.mqh, namely in the enumeration of graphical element types, add the new WinForms object type:
//+------------------------------------------------------------------+ //| 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_SHADOW_OBJ, // Shadow object GRAPH_ELEMENT_TYPE_ELEMENT, // Element GRAPH_ELEMENT_TYPE_FORM, // Form GRAPH_ELEMENT_TYPE_WINDOW, // Window //--- WinForms GRAPH_ELEMENT_TYPE_WF_UNDERLAY, // Panel object underlay GRAPH_ELEMENT_TYPE_WF_BASE, // Windows Forms Base //--- 'Container' object types are to be set below GRAPH_ELEMENT_TYPE_WF_CONTAINER, // Windows Forms container base object GRAPH_ELEMENT_TYPE_WF_PANEL, // Windows Forms Panel GRAPH_ELEMENT_TYPE_WF_GROUPBOX, // Windows Forms GroupBox //--- 'Standard control' object types are to be set below GRAPH_ELEMENT_TYPE_WF_COMMON_BASE, // Windows Forms base standard control GRAPH_ELEMENT_TYPE_WF_LABEL, // Windows Forms Label GRAPH_ELEMENT_TYPE_WF_BUTTON, // Windows Forms Button GRAPH_ELEMENT_TYPE_WF_CHECKBOX, // Windows Forms CheckBox GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON, // Windows Forms RadioButton GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX, // Windows Forms CheckedListBox }; //+------------------------------------------------------------------+
Add the new properties to the enumeration of graphical element integer properties and increase the total number of integer properties from 81 to 83:
//+------------------------------------------------------------------+ //| Integer properties of the graphical element on the canvas | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_PROP_INTEGER { CANV_ELEMENT_PROP_ID = 0, // Element ID CANV_ELEMENT_PROP_TYPE, // Graphical element type //---... //---... CANV_ELEMENT_PROP_ACT_BOTTOM, // Bottom border of the element active area CANV_ELEMENT_PROP_GROUP, // Group the graphical element belongs to CANV_ELEMENT_PROP_ZORDER, // Priority of a graphical object for receiving the event of clicking on a chart //---... //---... CANV_ELEMENT_PROP_BUTTON_TOGGLE, // Toggle flag of the control featuring a button CANV_ELEMENT_PROP_BUTTON_GROUP, // Button group flag CANV_ELEMENT_PROP_BUTTON_STATE, // Status of the Toggle control featuring a button //---... //---... CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_DOWN, // Color of control checkbox when clicking on the control CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER, // Color of control checkbox when hovering the mouse over the control }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (83) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting //+------------------------------------------------------------------+
The group the graphical element belongs to is a group index, while the button group flag is a flag indicating that the button works as part of several toggle buttons. If three buttons make up one toggle button object, then each of them should have a group button flag set, and they should be in the same group.
Add the new properties to the enumeration of possible criteria of sorting graphical elements on the 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 integer properties SORT_BY_CANV_ELEMENT_ID = 0, // Sort by element ID SORT_BY_CANV_ELEMENT_TYPE, // Sort by graphical element type //---... //---... SORT_BY_CANV_ELEMENT_ACT_BOTTOM, // Sort by the bottom border of the element active area SORT_BY_CANV_ELEMENT_GROUP, // Sort by a group the graphical element belongs to 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_BUTTON_TOGGLE, // Sort by the Toggle flag of the control featuring a button SORT_BY_CANV_ELEMENT_BUTTON_GROUP, // Sort by button group flag SORT_BY_CANV_ELEMENT_BUTTON_STATE, // Sort by the status of the Toggle control featuring a button SORT_BY_CANV_ELEMENT_CHECK_BACKGROUND_COLOR, // Sort by color of control checkbox background SORT_BY_CANV_ELEMENT_CHECK_BACKGROUND_COLOR_OPACITY, // Sort by opacity of control checkbox background color SORT_BY_CANV_ELEMENT_CHECK_BACKGROUND_COLOR_MOUSE_DOWN,// Sort by color of control checkbox background when clicking on the control SORT_BY_CANV_ELEMENT_CHECK_BACKGROUND_COLOR_MOUSE_OVER,// Sort by color of control checkbox background when hovering the mouse over the control SORT_BY_CANV_ELEMENT_CHECK_FORE_COLOR, // Sort by color of control checkbox frame SORT_BY_CANV_ELEMENT_CHECK_FORE_COLOR_OPACITY, // Sort by opacity of control checkbox frame color SORT_BY_CANV_ELEMENT_CHECK_FORE_COLOR_MOUSE_DOWN, // Sort by color of control checkbox frame when clicking on the control SORT_BY_CANV_ELEMENT_CHECK_FORE_COLOR_MOUSE_OVER, // Sort by color of control checkbox frame when hovering the mouse over the control SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR, // Sort by color of control checkbox SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR_OPACITY, // Sort by opacity of control checkbox color SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR_MOUSE_DOWN, // Sort by color of control checkbox when clicking on the control SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR_MOUSE_OVER, // Sort by color of control checkbox when hovering the mouse over the control //--- Sort by real properties //--- Sort by string properties SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Sort by an element object name SORT_BY_CANV_ELEMENT_NAME_RES, // Sort by the graphical resource name SORT_BY_CANV_ELEMENT_TEXT, // Sort by graphical element text }; //+------------------------------------------------------------------+
Now we will be able to sort and select graphical elements by new properties.
In \MQL5\Include\DoEasy\Data.mqh, add the new message indices:
//--- CPanel MSG_PANEL_OBJECT_ERR_FAILED_CREATE_UNDERLAY_OBJ, // Failed to create the underlay object MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE, // Error. The created object should be of WinForms Base type or be derived from it //--- CCheckedListBox MSG_CHECKED_LIST_ERR_FAILED_CREATE_CHECK_BOX_OBJ, // Failed to create the CheckBox object MSG_CHECKED_LIST_ERR_FAILED_GET_CHECK_BOX_OBJ, // Failed to get the CheckBox object from the object list //--- Integer properties of graphical elements
...
MSG_CANV_ELEMENT_PROP_AUTOCHECK, // Auto change flag status when it is selected MSG_CANV_ELEMENT_PROP_BUTTON_TOGGLE, // Toggle flag of the control featuring a button MSG_CANV_ELEMENT_PROP_BUTTON_GROUP, // Button group flag MSG_CANV_ELEMENT_PROP_BUTTON_STATE, // Status of the Toggle control featuring a button MSG_CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR, // Color of control checkbox background
and the text messages corresponding to the newly added indices:
//--- CPanel {"Не удалось создать объект-подложку","Failed to create underlay object"}, {"Ошибка. Создаваемый объект должен иметь тип WinForms Base или быть его наследником","Error. The object being created must be of type WinForms Base or be derived from it"}, //--- CCheckedListBox {"Не удалось создать объект CheckBox","Failed to create CheckBox object"}, {"Не удалось получить объект CheckBox из списка объектов","Failed to get CheckBox object from list of objects"}, //--- Integer properties of graphical elements
...
{"Автоматическое изменение состояния флажка при его выборе","Automatically change the state of the checkbox when it is selected"}, {"Флаг \"Переключатель\" элемента управления, имеющего кнопку","\"Button-toggle\" flag of a control"}, {"Флаг группы кнопки","Button group flag"}, {"Состояние элемента управления \"Переключатель\", имеющего кнопку","The \"Toggle-button\" control state"}, {"Цвет фона флажка проверки элемента управления","The background color of the control's validation checkbox"},
Slightly change the server type definition in the class constructor of the \MQL5\Include\DoEasy\Objects\Accounts\Account.mqh account object class file. As mentioned before, we will search for the "MetaTrader 5" substring within the terminal name string instead of getting the terminal name string:
//+------------------------------------------------------------------+ //| 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(); //--- Save integer properties this.m_long_prop[ACCOUNT_PROP_LOGIN] = ::AccountInfoInteger(ACCOUNT_LOGIN); this.m_long_prop[ACCOUNT_PROP_TRADE_MODE] = ::AccountInfoInteger(ACCOUNT_TRADE_MODE); this.m_long_prop[ACCOUNT_PROP_LEVERAGE] = ::AccountInfoInteger(ACCOUNT_LEVERAGE); this.m_long_prop[ACCOUNT_PROP_LIMIT_ORDERS] = ::AccountInfoInteger(ACCOUNT_LIMIT_ORDERS); this.m_long_prop[ACCOUNT_PROP_MARGIN_SO_MODE] = ::AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE); this.m_long_prop[ACCOUNT_PROP_TRADE_ALLOWED] = ::AccountInfoInteger(ACCOUNT_TRADE_ALLOWED); this.m_long_prop[ACCOUNT_PROP_TRADE_EXPERT] = ::AccountInfoInteger(ACCOUNT_TRADE_EXPERT); this.m_long_prop[ACCOUNT_PROP_MARGIN_MODE] = #ifdef __MQL5__::AccountInfoInteger(ACCOUNT_MARGIN_MODE) #else ACCOUNT_MARGIN_MODE_RETAIL_HEDGING #endif ; this.m_long_prop[ACCOUNT_PROP_CURRENCY_DIGITS] = #ifdef __MQL5__::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS) #else 2 #endif ; this.m_long_prop[ACCOUNT_PROP_SERVER_TYPE] = (::StringFind(::TerminalInfoString(TERMINAL_NAME),"MetaTrader 5")>WRONG_VALUE ? 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 ); //--- Save real properties this.m_double_prop[this.IndexProp(ACCOUNT_PROP_BALANCE)] = ::AccountInfoDouble(ACCOUNT_BALANCE); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_CREDIT)] = ::AccountInfoDouble(ACCOUNT_CREDIT); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_PROFIT)] = ::AccountInfoDouble(ACCOUNT_PROFIT); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_EQUITY)] = ::AccountInfoDouble(ACCOUNT_EQUITY); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN)] = ::AccountInfoDouble(ACCOUNT_MARGIN); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_FREE)] = ::AccountInfoDouble(ACCOUNT_MARGIN_FREE); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_LEVEL)] = ::AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_CALL)] = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_SO)] = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_SO); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_INITIAL)] = ::AccountInfoDouble(ACCOUNT_MARGIN_INITIAL); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_MAINTENANCE)]=::AccountInfoDouble(ACCOUNT_MARGIN_MAINTENANCE); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_ASSETS)] = ::AccountInfoDouble(ACCOUNT_ASSETS); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_LIABILITIES)] = ::AccountInfoDouble(ACCOUNT_LIABILITIES); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_COMMISSION_BLOCKED)]=::AccountInfoDouble(ACCOUNT_COMMISSION_BLOCKED); //--- Save string properties this.m_string_prop[this.IndexProp(ACCOUNT_PROP_NAME)] = ::AccountInfoString(ACCOUNT_NAME); this.m_string_prop[this.IndexProp(ACCOUNT_PROP_SERVER)] = ::AccountInfoString(ACCOUNT_SERVER); this.m_string_prop[this.IndexProp(ACCOUNT_PROP_CURRENCY)] = ::AccountInfoString(ACCOUNT_CURRENCY); this.m_string_prop[this.IndexProp(ACCOUNT_PROP_COMPANY)] = ::AccountInfoString(ACCOUNT_COMPANY); //--- Account object name, object and account type (MetaTrader 5 or 4) this.m_name=CMessage::Text(MSG_LIB_PROP_ACCOUNT)+" "+(string)this.Login()+": "+this.Name()+" ("+this.Company()+")"; this.m_type=COLLECTION_ACCOUNT_ID; this.m_type_server=(uchar)this.m_long_prop[ACCOUNT_PROP_SERVER_TYPE]; //--- Filling in the current account data for(int i=0;i<ACCOUNT_PROP_INTEGER_TOTAL;i++) this.m_long_prop_event[i][3]=this.m_long_prop[i]; for(int i=0;i<ACCOUNT_PROP_DOUBLE_TOTAL;i++) this.m_double_prop_event[i][3]=this.m_double_prop[i]; //--- Update the base object data and search for changes CBaseObjExt::Refresh(); } //+-------------------------------------------------------------------+
Add the value received above (5 or 4) to the m_type_server variable. Previously, it received the result of checking the terminal name for the "MetaTrader 5" value, which sometimes caused errors. Now it receives the value already set in the account property.
In the \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh file of the library base graphical object class, make the methods for setting and getting the object group virtual:
//--- 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; } virtual void SetGroup(const int value) { this.m_group=value; } void SetName(const string name) { this.m_name=name; } void SetDigits(const int value) { this.m_digits=value; }
...
virtual long Zorder(void) const { return this.m_zorder; } int SubWindow(void) const { return this.m_subwindow; } int ShiftY(void) const { return this.m_shift_y; } int VisibleOnTimeframes(void) const { return this.m_timeframes_visible; } int Digits(void) const { return this.m_digits; } virtual int Group(void) const { return this.m_group; } bool IsBack(void) const { return this.m_back; } bool IsSelected(void) const { return this.m_selected; } bool IsSelectable(void) const { return this.m_selectable; } bool IsHidden(void) const { return this.m_hidden; } bool IsVisible(void) const { return this.m_visible; }
We will need to redefine them in the inherited classes.
The GUI graphical elements are interconnected by a common hierarchy of their location relative to each other. The object the subordinate objects are bound to serves as the base object of a chain of related objects. In turn, subordinate objects can have their own chains of objects associated with them, while the base one, in turn, can be a link in the chain of objects bound to another one. In this case, the main object is considered to be the one that has subordinate objects but is not bound to any other object. It is the ancestor of the entire hierarchy of connections of all subordinate objects. Typically, such an object is a window in Windows and a form in C#. Here it will also be "Window", since the definition of "Form" is already occupied by a graphical element that implements the functionality for working with the mouse, and this object is the parent for the base WinForms object.
Implement the methods returning base and main object IDs in the MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh file of the graphical element class:
//--- Return the flag indicating that the object is (1) main, (2) base bool IsMain(void) { return this.GetMain()==NULL; } bool IsBase(void) { return this.GetBase()==NULL; } //--- Get the (1) main and (2) base object ID int GetMainID(void) { if(this.IsMain()) return this.ID(); CGCnvElement *main=this.GetMain(); return(main!=NULL ? main.ID() : WRONG_VALUE); } int GetBaseID(void) { if(this.IsBase()) return this.ID(); CGCnvElement *base=this.GetBase(); return(base!=NULL ? base.ID() : WRONG_VALUE); } //--- Return the pointer to a canvas object
Let's use getting the base object ID: if the object is already a base (contains subordinate objects), return its ID. Otherwise, get the pointer to the base object (these pointers are set in each subordinate object). If the pointer is received, return the base object ID, otherwise, return -1 (error — failed to get object).
The method logic for getting the main object ID is the same.
Write the virtual methods for getting and setting the graphical element group:
//--- Priority of a graphical object for receiving the event of clicking on a chart virtual long Zorder(void) const { return this.GetProperty(CANV_ELEMENT_PROP_ZORDER); } virtual bool SetZorder(const long value,const bool only_prop) { if(!CGBaseObj::SetZorder(value,only_prop)) return false; this.SetProperty(CANV_ELEMENT_PROP_ZORDER,value); return true; } //--- Graphical object group virtual int Group(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_GROUP); } virtual void SetGroup(const int value) { CGBaseObj::SetGroup(value); this.SetProperty(CANV_ELEMENT_PROP_GROUP,value); } //+------------------------------------------------------------------+
The Group() method returns the value set in the object "group" property.
In the method for setting the "group" property, first set the parent object value passed to the method. Next, set it to the graphical element property.
Each graphical element in the collection should have its own unique ID. This will allow us to refer directly to the object by its ID (if it is stored in the program), and not to look for it in loops through all objects. Currently, unique IDs are assigned only to those graphical elements that are created directly from the program. If we create (also programmatically) new bound objects from already created ones (which are bound to the object the new one is being created from), then the newly created subordinate object receives the ID of the object it was created from. In this situation, we can get this object by the ID of the base one with the number of the object specified in the list of subordinates.
Of course, this approach also works, but to simplify the work with programs created on the basis of the library, we will search for a unique ID and assign it to the newly created subordinate graphical element. Thus, we will assign a unique ID to each graphical element, by which we can access it. Secondly, we will still have the working method implemented at the moment — referring to the base object and getting the required element from it list by the element index. Two ways of getting pointers to objects is definitely better than one, especially since there will now be a faster way to access an element by its unique ID.
In the public section of the \MQL5\Include\DoEasy\Objects\Graph\Form.mqh file of the form object class, declare four methods for searching for the maximum object property value and ID:
//--- Return (1) itself, the list of (2) attached objects, (3) the list of interaction objects and (4) shadow object CForm *GetObject(void) { return &this; } CArrayObj *GetListElements(void) { return &this.m_list_elements; } CArrayObj *GetListInteractObj(void) { return &this.m_list_interact; } CShadowObj *GetShadowObj(void) { return this.m_shadow_obj; } //--- Return the maximum value (1) of the specified integer property and (2) ID from all objects bound to the base one long GetMaxLongPropForm(const ENUM_CANV_ELEMENT_PROP_INTEGER prop); int GetMaxIDForm(void); //--- Return the maximum value (1) of the specified integer property and (2) ID from the entire hierarchy of related objects long GetMaxLongPropAll(const ENUM_CANV_ELEMENT_PROP_INTEGER prop); int GetMaxIDAll(void); //--- Return the pointer to (1) the animation object, the list of (2) text and (3) rectangular animation frames
Methods with parameters will return the found maximum value of the specified property, while methods without parameters will return the found maximum value of the graphical element ID.
Change setting the ID of a newly created object in the method for creating a new attached element and adding it to the list of bound objects CreateAndAddNewElement().
Previously, we set an ID of an object a new one was created from:
obj.SetID(this.ID());
Now we will set the ID as a found maximum ID from the entire hierarchy of subordinate objects starting from the main one and add 1 to the obtained value. In other words, the new object will have the largest value of all the IDs of all the objects in the collection:
//+------------------------------------------------------------------+ //| Create a new attached element | //| and add it to the list of bound objects | //+------------------------------------------------------------------+ CGCnvElement *CForm::CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity) { //--- If the type of a created graphical element is less than the "element", inform of that and return 'false' if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),19)); return NULL; } //--- ... //--- ... //--- ... //--- Set the minimum properties for a bound graphical element obj.SetBackgroundColor(colour,true); obj.SetOpacity(opacity); obj.SetActive(activity); obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain()); obj.SetBase(this.GetObject()); obj.SetID(this.GetMaxIDAll()+1); obj.SetNumber(num); obj.SetCoordXRelative(obj.CoordX()-this.CoordX()); obj.SetCoordYRelative(obj.CoordY()-this.CoordY()); obj.SetZorder(this.Zorder(),false); obj.SetCoordXRelativeInit(obj.CoordXRelative()); obj.SetCoordYRelativeInit(obj.CoordYRelative()); return obj; } //+------------------------------------------------------------------+
After the cursor is moved away from the area of the graphical element, we turn on the handler for this event in order to restore the colors of the background, text and frame of the object to the default values, because these colors change when the cursor is hovered over the area of the element to visually display the activity of the graphical element. During multiple testing, I noticed that colors are not always restored correctly. Sometimes you need to re-position the cursor on the form where these objects are located so that their color is reset to the original one. I will get rid of such omissions with the development of the visual component of the library objects. Let's add yet another condition to the last mouse event handler processing moving the cursor away from the graphical element:
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CForm::OnMouseEventPostProcessing(void) { ENUM_MOUSE_FORM_STATE state=GetMouseState(); switch(state) { //--- The cursor is outside the form, the mouse buttons are not clicked //--- The cursor is outside the form, any mouse button is clicked //--- The cursor is outside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL : if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED) { this.SetBackgroundColor(this.BackgroundColorInit(),false); this.SetBorderColor(this.BorderColorInit(),false); this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); this.Redraw(true); } break; //--- The cursor is inside the form, the mouse buttons are not clicked //--- The cursor is inside the form, any mouse button is clicked //--- The cursor is inside the form, the mouse wheel is being scrolled //--- The cursor is inside the active area, the mouse buttons are not clicked //--- The cursor is inside the active area, any mouse button is clicked //--- The cursor is inside the active area, the mouse wheel is being scrolled //--- The cursor is inside the active area, left mouse button is released //--- The cursor is within the window scrolling area, the mouse buttons are not clicked //--- The cursor is within the window scrolling area, any mouse button is clicked //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL : break; //--- MOUSE_EVENT_NO_EVENT default: break; } } //+------------------------------------------------------------------+
Now we will process not only the situation when the cursor was in the active area of the object, but also the situation when the cursor was in the area of the object, since the active area does not always have dimensions of the entire area of the element, and the cursor, before going beyond the form from the active zone, falls into the inactive one, but within the graphical element. Now this situation is also taken into account.
The method returning the maximum value of the specified integer property of all objects subordinate to the base one:
//+------------------------------------------------------------------+ //| Return the maximum value of the specified integer | //| property from all objects subordinate to the base one | //+------------------------------------------------------------------+ long CForm::GetMaxLongPropForm(const ENUM_CANV_ELEMENT_PROP_INTEGER prop) { //--- Get the pointer to the base object CForm *base=this.GetBase(); //--- If the base object is received, then set the property value of the specified property, otherwise the value is equal to -1 long property=(base!=NULL ? base.GetProperty(prop) : WRONG_VALUE); //--- If the received value is greater than -1 if(property>WRONG_VALUE) { //--- In the loop through the list of bound objects for(int i=0;i<this.ElementsTotal();i++) { //--- get the next object CForm *elm=this.GetElement(i); if(elm==NULL) continue; //--- If the property value of the received object is greater than the value set in the property, //--- set the property value of the current object to 'property' if(elm.GetProperty(prop)>property) property=elm.GetProperty(prop); //--- Get the maximum property value from objects bound to the current one long prop_form=elm.GetMaxLongPropForm(prop); //--- If the received value is greater than the 'property' value, //--- set the received value to 'property' if(prop_form>property) property=prop_form; } } //--- Return the found maximum property value return property; } //+------------------------------------------------------------------+
The method logic has been described in the code comments in detail. This method searches for the maximum value of the specified integer property that has the value of zero or greater (non-negative) of all objects bound to the form. In this case, the value of the property of the base object is taken into account, and the search begins from it. Since all subordinate objects are created from the base object one after another, there cannot be a situation where we miss the maximum value of the property from those objects that are directly attached to the base, and start the search from an object located far from the base one in terms of hierarchy.
However, if we ever need to search exactly through the entire hierarchy of the base object, then we can easily create such a method.
The method returning the maximum value of the graphical element ID from all objects bound to the base one:
//+------------------------------------------------------------------+ //| Returns the maximum value of an ID | //| from all bound objects | //+------------------------------------------------------------------+ int CForm::GetMaxIDForm(void) { return (int)this.GetMaxLongPropForm(CANV_ELEMENT_PROP_ID); } //+------------------------------------------------------------------+
The method simply returns the result of the above method the "object ID" property is passed to for searching.
The method returning the maximum value of the specified integer property from the entire hierarchy of related objects:
//+------------------------------------------------------------------+ //| Return the maximum value of the specified integer | //| property from the entire hierarchy of related objects | //+------------------------------------------------------------------+ long CForm::GetMaxLongPropAll(const ENUM_CANV_ELEMENT_PROP_INTEGER prop) { //--- Get the pointer to the main object CForm *main=(this.IsMain() ? this.GetObject() : this.GetMain()); //--- If the main object is obtained, then set the value of the specified property to 'property', otherwise the value is equal to -1 long property=(main!=NULL ? main.GetProperty(prop) : WRONG_VALUE); //--- If the received value is greater than -1 if(property>WRONG_VALUE) { //--- In the loop through the list of objects bound to the main object for(int i=0;i<main.ElementsTotal();i++) { //--- get the next object CForm *elm=main.GetElement(i); if(elm==NULL) continue; //--- Get the maximum value of the property from the entire hierarchy of objects subordinate to the current one in the loop long prop_form=elm.GetMaxLongPropForm(prop); //--- If the received value is greater than the 'property' value, //--- set the received value to 'property' if(prop_form>property) property=prop_form; } } //--- Return the found maximum property value return property; } //+------------------------------------------------------------------+
The method logic is described in detail in the comments to the code, and it is similar to the method of finding the maximum property of objects attached to the base one. Unlike the first one, in this method, we start the search from the main object — the ancestor of the hierarchy of related objects — and go through the lists of all objects of the entire hierarchical chain of related objects. As a result, we have the largest property value of the entire hierarchy of subordinate objects.
The method returning the maximum value of the ID from the entire hierarchy of related objects:
//+------------------------------------------------------------------+ //| Returns the maximum value of an ID | //| from the entire hierarchy of related objects | //+------------------------------------------------------------------+ int CForm::GetMaxIDAll(void) { return (int)this.GetMaxLongPropAll(CANV_ELEMENT_PROP_ID); } //+------------------------------------------------------------------+
The method returns the result of calling the above method. In the parameters of this method, the "ID" property is passed to search for its maximum value.
The descriptions of graphical element properties are implemented in the class of the base WinForms object in \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh.
In the GetPropertyDescription() method, add code blocks for returning the description of two new graphical element properties:
property==CANV_ELEMENT_PROP_ACT_BOTTOM ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ACT_BOTTOM)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_GROUP ? CMessage::Text(MSG_GRAPH_OBJ_PROP_GROUP)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_ZORDER ? CMessage::Text(MSG_GRAPH_OBJ_PROP_ZORDER)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) :
...
property==CANV_ELEMENT_PROP_BUTTON_TOGGLE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BUTTON_TOGGLE)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)(bool)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_BUTTON_GROUP ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BUTTON_GROUP)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)(bool)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_BUTTON_STATE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BUTTON_STATE)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)(bool)this.GetProperty(property) ) :
Now the method will correctly return the descriptions of all graphical element properties.
The RadioButton WinForms object can only work correctly in conjunction with other objects of this type. In addition, these objects should either be bound to the same container, or have the same group value (be part of the same group of objects). If one of these objects is clicked, its checkbox will be selected (if it was not selected before), and all other objects of this group will have their checkboxes unchecked. When you click on an already selected object again, its flag is not cleared.
Let's make improvements to the RadioButton object class in \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\RadioButton.mqh.
In the private section of the class, declare the method setting "not selected" state to all objects of the same group. In the public section, implement the method setting the specified object and its checkbox status:
//+------------------------------------------------------------------+ //| CheckBox object class of the WForms controls | //+------------------------------------------------------------------+ class CRadioButton : public CCheckBox { private: //--- Set the state of the checkbox as "not selected" for all RadioButtons of the same group in the container void UncheckOtherAll(void); protected: //--- Displays the checkbox for the specified state virtual void ShowControlFlag(const ENUM_CANV_ELEMENT_CHEK_STATE state); //--- 'The cursor is inside the active area, the left mouse button is clicked' event handler virtual void MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); public: //--- Set the checkbox status virtual void SetChecked(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_CHECKED,flag); this.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)flag); if(this.Checked()) this.UncheckOtherAll(); } //--- Constructor
In the checkbox status setting method, implement the value passed to the method to its property, then set the selection state (either selected or not). Further on, if the state of the object is "selected", then we call the method, in which all other similar objects of this group are set to "not selected", and the checkbox is disabled.
In "The cursor is inside the active area, the left mouse button is clicked" event handler, add the code block, in which the object status is checked. If it is not selected, call the method for setting the object to "selected":
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| left mouse button released | //+------------------------------------------------------------------+ void CRadioButton::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- The mouse button released outside the element means refusal to interact with the element if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { this.SetCheckBackgroundColor(this.BackgroundColorInit(),false); this.SetCheckBorderColor(this.CheckBorderColorInit(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel")); } //--- The mouse button released within the element means a click on the control else { this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseOver(),false); this.SetCheckBorderColor(this.CheckBorderColorMouseOver(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); if(!this.Checked()) this.SetChecked(true); Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.Checked()=",this.Checked(),", ID=",this.ID(),", Group=",this.Group()); } this.Redraw(false); } //+------------------------------------------------------------------+
Here, a debug entry is displayed in the log indicating the event, the state of the element (selected/not selected), its ID and group index. Later, I will remove this entry and replace it with sending a message to the library and the control program.
The method that sets the state of the checkbox as "not selected" for all RadioButton objects of the same group in the container:
//+------------------------------------------------------------------+ //| Sets the state of the checkbox to "not selected" | //| for all RadioButton objects of the same group in the container | //+------------------------------------------------------------------+ void CRadioButton::UncheckOtherAll(void) { //--- Get the pointer to the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- From the base object, get a list of all objects of the RadioButton type CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON); //--- Select all objects from the received list, except for the given one (the names of the selected objects are not equal to the name of this one) list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,this.Name(),NO_EQUAL); //--- From the received list, select only those objects whose group index matches the group of the current one list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_GROUP,this.Group(),EQUAL); //--- If the list of objects is received, if(list!=NULL) { //--- in the loop through all objects in the list for(int i=0;i<list.Total();i++) { //--- get the next object, CRadioButton *obj=list.At(i); if(obj==NULL) continue; //--- set its state to "not selected" obj.SetChecked(false); //--- Redraw the object to display an unselected checkbox obj.Redraw(false); } } } //+------------------------------------------------------------------+
Each line of code is commented. I hope, the logic of the method will not cause any questions. In short, we need to get a list of all RadioButton objects bound to the container. They should all be of the same group, and the list should not contain the object the method was called from (after all, this is the object clicked by the mouse and it became selected, which means that we do not need to remove the selection flag from it). Loop through the resulting list and set each of the objects to an unselected state and uncheck the box. Objects are redrawn to reflect the changes.
In \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh button object class file, make similar improvements, which will make the buttons not just clickable but able to have the enabled/disabled state. Accordingly, we will be able to assign them to groups, in which buttons connected by one group will work together.
In the private section of the class, declare the method setting the "released" status to all buttons of the same group:
//+------------------------------------------------------------------+ //| Label object class of WForms controls | //+------------------------------------------------------------------+ class CButton : public CLabel { private: int m_text_x; // Text X coordinate int m_text_y; // Text Y coordinate color m_array_colors_bg_tgl[]; // Array of element background colors for the 'enabled' state color m_array_colors_bg_tgl_dwn[]; // Array of control background colors for the 'enabled' state when clicking on the control color m_array_colors_bg_tgl_ovr[]; // Array of control background colors for the 'enabled' state when hovering the mouse over the control color m_array_colors_bg_tgl_init[]; // Array of initial element background colors for the 'enabled' state //--- Set the button state as "released" for all Buttons of the same group in the container void UnpressOtherAll(void); protected:
In the public section of the class, change the method setting the button status and implement the methods to set and return the flag of a button working in a group with other button objects:
//--- (1) Set and (2) return the Toggle control status void SetState(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_BUTTON_STATE,flag); if(this.State()) { this.UnpressOtherAll(); } } bool State(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_STATE); } //--- (1) Set and (2) return the group flag void SetGroupButtonFlag(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP,flag); } bool GroupButton(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP); }
In the method that sets the state of the button, the state is first set in the object property, and then, if the state is "pressed", we call the method that sets the state of the rest of the buttons of the same group to "released".
In the last mouse event handler, add checking yet another condition similar to the form object class method of the same name considered above:
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CButton::OnMouseEventPostProcessing(void) { ENUM_MOUSE_FORM_STATE state=GetMouseState(); switch(state) { //--- The cursor is outside the form, the mouse buttons are not clicked //--- The cursor is outside the form, any mouse button is clicked //--- The cursor is outside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL : if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED) { this.SetBackgroundColor(this.State() ? this.BackgroundColorToggleON() : this.BackgroundColorInit(),false); this.SetBorderColor(this.BorderColorInit(),false); this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); this.Redraw(false); } break; //--- The cursor is inside the form, the mouse buttons are not clicked //--- The cursor is inside the form, any mouse button is clicked //--- The cursor is inside the form, the mouse wheel is being scrolled //--- The cursor is inside the active area, the mouse buttons are not clicked //--- The cursor is inside the active area, any mouse button is clicked //--- The cursor is inside the active area, the mouse wheel is being scrolled //--- The cursor is inside the active area, left mouse button is released //--- The cursor is within the window scrolling area, the mouse buttons are not clicked //--- The cursor is within the window scrolling area, any mouse button is clicked //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL : break; //--- MOUSE_EVENT_NO_EVENT default: break; } } //+------------------------------------------------------------------+
The method that sets the button state as "released" for all Button objects of the same group in the container:
//+------------------------------------------------------------------+ //| Sets the state of the button to "released" | //| for all Buttons of the same group in the container | //+------------------------------------------------------------------+ void CButton::UnpressOtherAll(void) { //--- Get the pointer to the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- Get the list of all objects of the Button type from the base object CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_BUTTON); //--- Select all objects from the received list, except for the given one (the names of the selected objects are not equal to the name of this one) list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,this.Name(),NO_EQUAL); //--- From the received list, select only those objects whose group index matches the group of the current one list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_GROUP,this.Group(),EQUAL); //--- If the list of objects is received, if(list!=NULL) { //--- in the loop through all objects in the list for(int i=0;i<list.Total();i++) { //--- get the next object, CButton *obj=list.At(i); if(obj==NULL) continue; //--- set the button status to "released", obj.SetState(false); //--- set the background color to the original one (the cursor is on another button outside this one) obj.SetBackgroundColor(obj.BackgroundColorInit(),false); //--- Redraw the object to display the changes obj.Redraw(false); } } } //+------------------------------------------------------------------+
The method logic is identical to the method of the RadioButton object class. It is fully described in the code comments and, I hope, does not need explanations.
In its normal state, when you hover the cursor over it, the CheckBox WinForms object changes the color of the background, checkbox and frame of the checkbox area. The background of the object itself remains unchanged (transparent). But if such objects are combined into a group (as is the case with the object coming next), then when you hover over the object with the cursor, its background color changes as well. In order to use the CheckBox object to create an object list of CheckBox objects, we will make changes to this object in \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh.
Make the method for setting the checkbox status virtual, just like in its RadioButton child object:
//--- (1) Set and (2) return the checkbox status virtual void SetChecked(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_CHECKED,flag); if((bool)this.CheckState()!=flag) this.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)flag); } bool Checked(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_CHECKED); }
Set the object background color to full opacity in the class constructor and set the shift of the active area by one pixel on each side:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CCheckBox::CCheckBox(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CLabel(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CHECKBOX); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetCoordX(x); this.SetCoordY(y); this.SetCheckWidth(DEF_CHECK_SIZE); this.SetCheckHeight(DEF_CHECK_SIZE); this.SetWidth(w); this.SetHeight(h); this.Initialize(); this.SetOpacity(0); this.SetForeColorMouseDown(CLR_DEF_FORE_COLOR_MOUSE_DOWN); this.SetForeColorMouseOver(CLR_DEF_FORE_COLOR_MOUSE_OVER); this.SetCheckBackgroundColor(CLR_DEF_CHECK_BACK_COLOR,true); this.SetCheckBackgroundColorMouseDown(CLR_DEF_CHECK_BACK_MOUSE_DOWN); this.SetCheckBackgroundColorMouseOver(CLR_DEF_CHECK_BACK_MOUSE_OVER); this.SetCheckBorderColor(CLR_DEF_CHECK_BORDER_COLOR,true); this.SetCheckBorderColorMouseDown(CLR_DEF_CHECK_BORDER_MOUSE_DOWN); this.SetCheckBorderColorMouseOver(CLR_DEF_CHECK_BORDER_MOUSE_OVER); this.SetCheckFlagColor(CLR_DEF_CHECK_FLAG_COLOR,true); this.SetCheckFlagColorMouseDown(CLR_DEF_CHECK_FLAG_MOUSE_DOWN); this.SetCheckFlagColorMouseOver(CLR_DEF_CHECK_FLAG_MOUSE_OVER); this.SetWidthInit(this.Width()); this.SetHeightInit(this.Height()); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetTextAlign(ANCHOR_LEFT); this.SetActiveAreaShift(1,1,1,1); this.m_text_x=0; this.m_text_y=0; this.m_check_x=0; this.m_check_y=0; this.Redraw(false); } //+------------------------------------------------------------------+
Here we set the background to full opacity because we will need to further create objects with the opacity of the base object. In order to avoid constantly setting the opacity of this object in its normal state when it is created, we will explicitly set it here in the constructor. Shifting the active area by one pixel on each side is an attempt to increase the gap between adjacent CheckBox objects so that when the cursor moves away from one object, it has time to "visit" the object, without immediately hovering over the next one - so that the cursor passes through the base object without immediately hitting the nearby one. All of this is the result of a search for a solution to the issue when nearby objects do not always restore their default background color after moving the cursor away from it. However, such a solution does not always help. I still have to find the time to thoroughly grasp and fix the issue.
In the method redrawing an object, we now specify the opacity value specified in the object properties instead of setting the full opacity (the value of 0):
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CCheckBox::Redraw(bool redraw) { //--- Fill the object with the background color having full transparency this.Erase(this.BackgroundColor(),this.Opacity(),true); //--- Set corrected text coordinates relative to the checkbox this.SetCorrectTextCoords(); //--- Draw the text and checkbox within the set coordinates of the object and the binding point, and update the object this.Text(this.m_text_x,this.m_text_y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor()); this.ShowControlFlag(this.CheckState()); this.Update(redraw); } //+------------------------------------------------------------------+
This will allow us to use the background color to change it when hovering the mouse over the object. With an opaque background (as was the case before), no background color changes can be displayed, of course.
Implement the necessary changes to all mouse event handlers requiring the change of the object background color:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CCheckBox::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseOver(),false); this.SetCheckBorderColor(this.CheckBorderColorMouseOver(),false); this.SetCheckFlagColor(this.CheckFlagColorMouseOver(),false); this.SetBackgroundColor(this.BackgroundColorMouseOver(),false); this.Redraw(false); } //+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| a mouse button is clicked (any) | //+------------------------------------------------------------------+ void CCheckBox::MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseDown(),false); this.SetCheckBorderColor(this.CheckBorderColorMouseDown(),false); this.SetCheckFlagColor(this.CheckFlagColorMouseDown(),false); this.SetBackgroundColor(this.BackgroundColorMouseDown(),false); this.Redraw(false); } //+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| left mouse button released | //+------------------------------------------------------------------+ void CCheckBox::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- The mouse button released outside the element means refusal to interact with the element if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { this.SetCheckBackgroundColor(this.CheckBackgroundColorInit(),false); this.SetCheckBorderColor(this.CheckBorderColorInit(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); this.SetBackgroundColor(this.BackgroundColorInit(),false); //--- Send a test entry to the journal Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel")); } //--- The mouse button released within the element means a click on the control else { this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseOver(),false); this.SetCheckBorderColor(this.CheckBorderColorMouseOver(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); this.SetBackgroundColor(this.BackgroundColorMouseOver(),false); this.SetChecked(!this.Checked()); //--- Send a test entry to the journal Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.Checked()=",this.Checked(),", ID=",this.ID()); } this.Redraw(false); } //+------------------------------------------------------------------+
Add checking the already familiar status to the last mouse event handler:
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CCheckBox::OnMouseEventPostProcessing(void) { ENUM_MOUSE_FORM_STATE state=GetMouseState(); switch(state) { //--- The cursor is outside the form, the mouse buttons are not clicked //--- The cursor is outside the form, any mouse button is clicked //--- The cursor is outside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL : if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED) { this.SetBackgroundColor(this.BackgroundColorInit(),false); this.SetBorderColor(this.BorderColorInit(),false); this.SetCheckBackgroundColor(this.CheckBackgroundColorInit(),false); this.SetCheckBorderColor(this.CheckBorderColorInit(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); this.Redraw(false); } break; //--- The cursor is inside the form, the mouse buttons are not clicked //--- The cursor is inside the form, any mouse button is clicked //--- The cursor is inside the form, the mouse wheel is being scrolled //--- The cursor is inside the active area, the mouse buttons are not clicked //--- The cursor is inside the active area, any mouse button is clicked //--- The cursor is inside the active area, the mouse wheel is being scrolled //--- The cursor is inside the active area, left mouse button is released //--- The cursor is within the window scrolling area, the mouse buttons are not clicked //--- The cursor is within the window scrolling area, any mouse button is clicked //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL : break; //--- MOUSE_EVENT_NO_EVENT default: break; } } //+------------------------------------------------------------------+
Now we can start creating a new object.
CheckedListBox WinForms object
This WinForms object is a panel containing the list of CheckBox objects. When hovering the cursor over the list objects, their background color changes along with the color of the checkbox, its background and the border of the checkbox area. The objects from the list can be located both vertically one above the other, and in columns of several pieces. Today we will only do the vertical arrangement of objects.
In the \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ library directory, create the new file CheckedListBox.mqh of the CCheckedListBox class.
The class should be inherited from the base container object. To let it "see" both CContainer and CCheckBox classes, include the panel object class file, in which all the necessary class files are already visible:
//+------------------------------------------------------------------+ //| CheckedListBox.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Containers\Panel.mqh" //+------------------------------------------------------------------+ //| CCheckedListBox object Class of the WForms controls | //+------------------------------------------------------------------+ class CCheckedListBox : public CContainer { }
In the private section, declare the virtual method for creating a new graphical object, while in the public section, declare the method for creating the list consisting of the specified number of CheckBox objects, as well as the parametric constructor:
//+------------------------------------------------------------------+ //| CCheckedListBox object Class of the WForms controls | //+------------------------------------------------------------------+ class CCheckedListBox : public CContainer { private: //--- Create a new graphical object virtual 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); public: //--- Create the specified number of CheckBox objects void CreateCheckBox(const int count); //--- Constructor CCheckedListBox(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
Parametric constructor:
//+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CCheckedListBox::CCheckedListBox(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CContainer(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetBorderSizeAll(1); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBorderColor(CLR_DEF_BORDER_COLOR,true); this.SetForeColor(CLR_DEF_FORE_COLOR,true); } //+------------------------------------------------------------------+
Set the WinForms object type and the library graphical object type for the object. Next, set the size of the object frame to one pixel, the frame type is simple. Also, set the default frame and text color for graphical objects in the library.
The method that creates the specified number of CheckBox objects on the main panel:
//+------------------------------------------------------------------+ //| Create the specified number of CheckBox objects | //+------------------------------------------------------------------+ void CCheckedListBox::CreateCheckBox(const int count) { //--- Create a pointer to the CheckBox object CCheckBox *cbox=NULL; //--- In the loop through the specified number of objects for(int i=0;i<count;i++) { //--- Set the coordinates and dimensions of the created object int x=this.BorderSizeLeft()+1; int y=(cbox==NULL ? this.BorderSizeTop()+1 : cbox.BottomEdgeRelative()+4); int w=this.Width()-this.BorderSizeLeft()-this.BorderSizeRight(); int h=DEF_CHECK_SIZE+1; //--- If the object could not be created, display a message in the log if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,x,y,w,h,clrNONE,255,true,false)) CMessage::ToLog(DFUN,MSG_CHECKED_LIST_ERR_FAILED_CREATE_CHECK_BOX_OBJ); //--- Get the created object from the list by the loop index cbox=this.GetElement(i); //--- If the object could not be obtained, display a message in the log if(cbox==NULL) CMessage::ToLog(DFUN,MSG_CHECKED_LIST_ERR_FAILED_GET_CHECK_BOX_OBJ); //--- Set the frame size to zero cbox.SetBorderSizeAll(0); //--- Set the left center alignment of the checkbox and the text cbox.SetCheckAlign(ANCHOR_LEFT); cbox.SetTextAlign(ANCHOR_LEFT); //--- Set the object text cbox.SetText("CheckBox"+string(i+1)); //--- Set the opacity of the base object and the default background color cbox.SetOpacity(this.Opacity()); cbox.SetBackgroundColor(this.BackgroundColor(),true); cbox.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_STD_MOUSE_OVER); cbox.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_STD_MOUSE_DOWN); } //--- For the base object, set the auto resizing mode as "increase and decrease" //--- and set the auto resize flag this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,false); this.SetAutoSize(true,false); } //+------------------------------------------------------------------+
The method logic features detailed comments in the code. In short, the required number of CheckBox objects to be created on the panel is passed to the method. In the loop by the specified number of objects, create them and set the necessary properties for them. Upon completing the loop of creating CheckBox objects, set the auto resize mode of the panel so that it can be adjusted to the total size of all objects created on it. Also, set the auto resize flag for the panel, which in turn will cause the panel to be resized to fit the objects created in it.
The virtual method creating a new graphical object:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CCheckedListBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_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) { string name=this.CreateNameDependentObject(obj_name); //--- create the CheckBox object CGCnvElement *element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); //--- set the object relocation flag and relative coordinates element.SetMovable(movable); element.SetCoordXRelative(element.CoordX()-this.CoordX()); element.SetCoordYRelative(element.CoordY()-this.CoordY()); return element; } //+------------------------------------------------------------------+
Since one of the object parents is the class of the form object featuring the functionality for working with the mouse, and the class of this object is not visible in it, we need to override the virtual method of the CForm class here to create a new graphical object. In this method, we do not need to check the type of an object passed to the method, since here we know exactly which type of object is to be created. This type is CheckBox, which we are creating here and setting the minimum values for it — the relocation flag and relative coordinates.
All other methods for the class are already in its parent classes.
Naturally, I will refine the object class later to implement its additional functionality.
Now we need to add handling this type of object in all container classes so that we can create such objects in them.
In \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh file of the panel object class, include the file of the newly created class:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Container.mqh" #include "GroupBox.mqh" #include "..\..\WForms\Common Controls\CheckedListBox.mqh" //+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+
Now this new class will be visible in all container classes of the library.
In the method that creates a new graphical object, add handling the type of the new library object:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_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) { string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); return element; } //+------------------------------------------------------------------+
Here we simply create a new object of the CCheckedListBox class.
In the \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh file of the base container object class, namely in the method creating a new bound element, set the group similar to the one the base object has for the created object but only if the created object is not a container object. Add setting a transparent background color and its full transparency, as well as add handling the CheckedListBox WinForms object for the "Text label", "CheckBox" and "RadioButton" WinForms objects:
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CContainer::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw) { //--- If the object type is less than the base WinForms object if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE) { //--- report the error and return 'false' CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE); return false; } //--- If failed to create a new graphical element, return 'false' CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity); if(obj==NULL) return false; //--- Set the text color of the created object to be the same as that of the base container obj.SetForeColor(this.ForeColor(),true); //--- If the created object is not a container, set the same group for it as the one for its base object if(obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_CONTAINER || obj.TypeGraphElement()>GRAPH_ELEMENT_TYPE_WF_GROUPBOX) obj.SetGroup(this.Group()); //--- Depending on the created object type, switch(obj.TypeGraphElement()) { //--- For the Container, Panel and GroupBox WinForms objects case GRAPH_ELEMENT_TYPE_WF_CONTAINER : case GRAPH_ELEMENT_TYPE_WF_PANEL : case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : //--- set the frame color equal to the background color obj.SetBorderColor(obj.BackgroundColor(),true); break; //--- For the Text Label, CheckBox and RadioButton WinForms objects case GRAPH_ELEMENT_TYPE_WF_LABEL : case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : //--- set the object text color depending on the one passed to the method: //--- either the container text color, or the one passed to the method. //--- The frame color is set equal to the text color //--- Set the background color to transparent obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour,true); obj.SetBorderColor(obj.ForeColor(),true); obj.SetBackgroundColor(CLR_CANV_NULL,true); obj.SetOpacity(0,false); break; //--- For the Button WinForms object case GRAPH_ELEMENT_TYPE_WF_BUTTON : //--- set the object text color as a container text color depending on the one passed to the method: //--- set the background color depending on the one passed to the method: //--- either the default standard control background color, or the one passed to the method. //--- The frame color is set equal to the text color obj.SetForeColor(this.ForeColor(),true); obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true); obj.SetBorderColor(obj.ForeColor(),true); obj.SetBorderStyle(FRAME_STYLE_SIMPLE); break; //--- For the CheckedListBox WinForms object case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : //--- set the object text color as a container text color depending on the one passed to the method: //--- set the background color depending on the one passed to the method: //--- either the default standard control background color, or the one passed to the method. //--- The frame color is set equal to the text color obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true); obj.SetBorderColor(CLR_DEF_BORDER_COLOR,true); obj.SetForeColor(CLR_DEF_FORE_COLOR,true); break; default: break; } //--- If the panel has auto resize enabled and features bound objects, call the resize method if(this.AutoSize() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); //--- Redraw the panel and all added objects, and return 'true' this.Redraw(redraw); return true; } //+------------------------------------------------------------------+
In the \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh file of the GroupBox object class, namely in the method for creating a new graphical object, add handling the new type of the CheckedListBox object:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CGroupBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_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) { string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); return element; } //+------------------------------------------------------------------+
At the very end of the Initialize() variable initialization method, add setting the default group value:
this.SetEnabled(true); this.SetVisible(true,false); //--- Set the default group value this.SetGroup((int)this.GetMaxLongPropAll(CANV_ELEMENT_PROP_GROUP)+1); } //+------------------------------------------------------------------+
Here we call the previously considered method that returns the maximum value of the specified property from all objects in the collection of the library graphical elements. Specify the "group" property as the desired parameter, and add 1 to the resulting value, which will set the value of the new group to maximum. It is important to have a unique group that distinguishes it from other container objects.
In the \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh graphical element collection class file, add the file of the CheckedListBox object class to the list of include files:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Services\Select.mqh" #include "..\Objects\Graph\WForms\Containers\GroupBox.mqh" #include "..\Objects\Graph\WForms\Containers\Panel.mqh" #include "..\Objects\Graph\WForms\Common Controls\CheckedListBox.mqh" #include "..\Objects\Graph\Standard\GStdVLineObj.mqh" #include "..\Objects\Graph\Standard\GStdHLineObj.mqh"
In the private class section, declare the method returning the maximum ID from all collection graphical elements:
//--- Return the screen coordinates of each pivot point of the graphical object bool GetPivotPointCoordsAll(CGStdGraphObj *obj,SDataPivotPoint &array_pivots[]); //--- Return the maximum ID from all graphical elements of the collection int GetMaxID(void); public:
In all the graphical element creation methods, replace the string setting the total number of graphical elements in the collection as the ID,
int id=this.m_list_all_canv_elm_obj.Total();
with the string assigning the maximum ID out of all collection graphical elements plus 1:
//--- 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.GetMaxID()+1; CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,activity,redraw); ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id); if(res==ADD_OBJ_RET_CODE_ERROR) return WRONG_VALUE; if(res==ADD_OBJ_RET_CODE_EXIST) obj.SetID(id); 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
Such changes have been made in all methods that create graphical elements. I will not provide them here. All such changes can be found in the library files attached to the article.
In the method handling the former active form under the cursor, add calling the mouse event handler for the current loop object to avoid skipping its handling in case the object is not in the list of inactive objects yet when the cursor is moved away from it, but is, in fact, inactive already:
//+------------------------------------------------------------------+ //| Post-processing of the former active form under the cursor | //+------------------------------------------------------------------+ void CGraphElementsCollection::FormPostProcessing(void) { //--- Get all the elements of the CForm type and above CArrayObj *list=GetList(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_FORM,EQUAL_OR_MORE); if(list==NULL) return; //--- In the loop by the list of received elements int total=list.Total(); for(int i=0;i<total;i++) { //--- get the pointer to the object CForm *obj=list.At(i); //--- if failed to get the pointer, move on to the next one in the list if(obj==NULL) continue; obj.OnMouseEventPostProcessing(); //--- Create the list of interaction objects and get their number int count=obj.CreateListInteractObj(); //--- In the loop by the obtained list for(int j=0;j<count;j++) { //--- get the next object CForm *elm=obj.GetInteractForm(j); if(elm==NULL) continue; //--- and call its method of handling mouse events elm.OnMouseEventPostProcessing(); } } } //+------------------------------------------------------------------+
In order to implement handling the interaction of the right mouse button with the program GUI objects, add the check for pressing and holding the right mouse button in the OnChartEvent() event handler:
//--- Handling mouse events of graphical objects on canvas //--- If the event is not a chart change else { //--- Check whether the mouse button is clicked ENUM_MOUSE_BUTT_KEY_STATE butt_state=this.m_mouse.ButtonKeyState(id,lparam,dparam,sparam); bool pressed=(butt_state==MOUSE_BUTT_KEY_STATE_LEFT || butt_state==MOUSE_BUTT_KEY_STATE_RIGHT ? true : false); ENUM_MOUSE_FORM_STATE mouse_state=MOUSE_FORM_STATE_NONE; //--- Declare static variables for the active form and status flags
The state of the mouse buttons will be set to a variable, so that later, if we need this value, we do not call the method that reads the state of the buttons again.
The method that returns the maximum ID from all graphical elements in the collection:
//+------------------------------------------------------------------+ //| Return the maximum ID | //| of all graphical elements of the collection | //+------------------------------------------------------------------+ int CGraphElementsCollection::GetMaxID(void) { int index=CSelect::FindGraphCanvElementMax(this.GetListCanvElm(),CANV_ELEMENT_PROP_ID); CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(index); return(obj!=NULL ? obj.ID() : WRONG_VALUE); } //+------------------------------------------------------------------+
Here we get the index of the object in the collection list having the highest ID value. We get the pointer to an object by a received index and return the object ID if the pointer to the object was received. Otherwise, return -1. Either the collection of graphical elements is empty, or there has been an error when getting the pointer.
Test
To perform the test, let's use the EA from the previous article and save it to \MQL5\Experts\TestDoEasy\Part111\ as TstDE111.mq5.
Make the main panel larger in size and place the CheckBox object in its first container object together with four RadioButton objects with the group value of 2, three button objects, two of which will have the group 2, while the third one will belong to the group 1 inherited by default from its container object group. Below the buttons, place two more RadioButton objects with the group value of 3. Thus, in the container, we will have four RadioButton objects with group 2, two RadioButton objects with group 3, and three buttons — two with group 2 and one with group 1.
To the right of the first GroupBox container, place another one of the same type and create a new CheckedListBox object inside it. The object will be used to create four CheckBox objects. All objects placed in different groups of the same container should work as separate sets of objects. The entire visual component of the interaction of objects with the mouse should work well.
In the OnInit() EA handler, place the following code block for creating all GUI elements:
//+------------------------------------------------------------------+ //| 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 WinForms Panel object CPanel *pnl=NULL; pnl=engine.CreateWFPanel("WFPanel",50,50,400,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false); if(pnl!=NULL) { //--- Set Padding to 4 pnl.SetPaddingAll(4); //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs pnl.SetMovable(InpMovable); pnl.SetAutoSize(InpAutoSize,false); pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false); //--- In the loop, create 2 bound panel objects CPanel *obj=NULL; for(int i=0;i<2;i++) { //--- create the panel object with calculated coordinates, width of 90 and height of 40 CPanel *prev=pnl.GetElement(i-1); int xb=0, yb=0; int x=(prev==NULL ? xb : xb+prev.Width()+20); int y=0; if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,x,y,90,40,C'0xCD,0xDA,0xD7',200,true,false)) { obj=pnl.GetElement(i); if(obj==NULL) continue; obj.SetBorderSizeAll(3); obj.SetBorderStyle(FRAME_STYLE_BEVEL); obj.SetBackgroundColor(obj.ChangeColorLightness(obj.BackgroundColor(),4*i),true); obj.SetForeColor(clrRed,true); //--- Calculate the width and height of the future text label object int w=obj.Width()-obj.BorderSizeLeft()-obj.BorderSizeRight(); int h=obj.Height()-obj.BorderSizeTop()-obj.BorderSizeBottom(); //--- Create a text label object obj.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LABEL,0,0,w,h,clrNONE,255,false,false); //--- Get the pointer to a newly created object CLabel *lbl=obj.GetElement(0); if(lbl!=NULL) { //--- If the object has an even or zero index in the list, set the default text color for it if(i % 2==0) lbl.SetForeColor(CLR_DEF_FORE_COLOR,true); //--- If the object index in the list is odd, set the object opacity to 127 else lbl.SetForeColorOpacity(127); //--- Set the font Black width type and //--- specify the text alignment from the EA settings lbl.SetFontBoldType(FW_TYPE_BLACK); lbl.SetTextAlign(InpTextAlign); lbl.SetAutoSize((bool)InpTextAutoSize,false); //--- For an object with an even or zero index, specify the Bid price for the text, otherwise - the Ask price of the symbol lbl.SetText(GetPrice(i % 2==0 ? SYMBOL_BID : SYMBOL_ASK)); //--- Set the frame width, type and color for a text label and update the modified object lbl.SetBorderSizeAll(1); lbl.SetBorderStyle((ENUM_FRAME_STYLE)InpFrameStyle); lbl.SetBorderColor(CLR_DEF_BORDER_COLOR,true); lbl.Update(true); } } } //--- Create the WinForms GroupBox1 object CGroupBox *gbox1=NULL; //--- The indent from the attached panels by 6 pixels will be the Y coordinate of GrotupBox1 int w=pnl.GetUnderlay().Width(); int y=obj.BottomEdgeRelative()+6; //--- If the attached GroupBox object is created if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,0,y,200,150,C'0x91,0xAA,0xAE',0,true,false)) { //--- get the pointer to the GroupBox object by its index in the list of bound GroupBox type objects gbox1=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,0); if(gbox1!=NULL) { //--- set the "indented frame" type, the frame color matches the main panel background color, //--- while the text color is the background color of the last attached panel darkened by 1 gbox1.SetBorderStyle(FRAME_STYLE_STAMP); gbox1.SetBorderColor(pnl.BackgroundColor(),true); gbox1.SetForeColor(gbox1.ChangeColorLightness(obj.BackgroundColor(),-1),true); gbox1.SetText("GroupBox1"); //--- Create the CheckBox object gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,2,10,50,20,clrNONE,255,true,false); //--- get the pointer to the CheckBox object by its index in the list of bound CheckBox type objects CCheckBox *cbox=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,0); //--- If CheckBox is created and the pointer to it is received if(cbox!=NULL) { //--- Set the CheckBox parameters from the EA inputs cbox.SetAutoSize((bool)InpCheckAutoSize,false); cbox.SetCheckAlign(InpCheckAlign); cbox.SetTextAlign(InpCheckTextAlign); //--- Set the displayed text, frame style and color, as well as checkbox status cbox.SetText("CheckBox"); cbox.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle); cbox.SetBorderColor(CLR_DEF_BORDER_COLOR,true); cbox.SetChecked(true); cbox.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)InpCheckState); } //--- Create 4 RadioButton WinForms objects CRadioButton *rbtn=NULL; for(int i=0;i<4;i++) { //--- Create the RadioButton object int yrb=(rbtn==NULL ? cbox.BottomEdgeRelative() : rbtn.BottomEdgeRelative()); gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,2,yrb+4,50,20,clrNONE,255,true,false); //--- get the pointer to the RadioButton object by its index in the list of bound RadioButton type objects rbtn=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,i); //--- If RadioButton1 is created and the pointer to it is received if(rbtn!=NULL) { //--- Set the RadioButton parameters from the EA inputs rbtn.SetAutoSize((bool)InpCheckAutoSize,false); rbtn.SetCheckAlign(InpCheckAlign); rbtn.SetTextAlign(InpCheckTextAlign); //--- Set the displayed text, frame style and color, as well as checkbox status rbtn.SetText("RadioButton"+string(i+1)); rbtn.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle); rbtn.SetBorderColor(CLR_DEF_BORDER_COLOR,true); rbtn.SetChecked(!i); rbtn.SetGroup(2); } } //--- Create 3 Button WinForms objects CButton *butt=NULL; for(int i=0;i<3;i++) { //--- Create the Button object int ybtn=(butt==NULL ? 12 : butt.BottomEdgeRelative()+4); gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON,(int)fmax(rbtn.RightEdgeRelative(),cbox.RightEdgeRelative())+20,ybtn,78,18,clrNONE,255,true,false); //--- get the pointer to the Button object by its index in the list of bound Button type objects butt=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,i); //--- If Button is created and the pointer to it is received if(butt!=NULL) { //--- Set the Button parameters from the EA inputs butt.SetAutoSize((bool)InpButtonAutoSize,false); butt.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpButtonAutoSizeMode,false); butt.SetTextAlign(InpButtonTextAlign); //--- Set the text color, as well as frame style and color butt.SetForeColor(butt.ChangeColorLightness(CLR_DEF_FORE_COLOR,2),true); butt.SetBorderStyle((ENUM_FRAME_STYLE)InpButtonFrameStyle); butt.SetBorderColor(butt.ChangeColorLightness(butt.BackgroundColor(),-10),true); //--- Set the 'toggle' mode depending on the settings butt.SetToggleFlag(InpButtonToggle); //--- Set the displayed text on the button depending on the 'toggle' flag string txt="Button"+string(i+1); if(butt.Toggle()) butt.SetText("Toggle-"+txt); else butt.SetText(txt); if(i<2) { butt.SetGroup(2); if(butt.Toggle()) { butt.SetBackgroundColorMouseOver(butt.ChangeColorLightness(butt.BackgroundColor(),-5)); butt.SetBackgroundColorMouseDown(butt.ChangeColorLightness(butt.BackgroundColor(),-10)); butt.SetBackgroundColorToggleON(C'0xE2,0xC5,0xB1',true); butt.SetBackgroundColorToggleONMouseOver(butt.ChangeColorLightness(butt.BackgroundColorToggleON(),-5)); butt.SetBackgroundColorToggleONMouseDown(butt.ChangeColorLightness(butt.BackgroundColorToggleON(),-10)); } } } } //--- Create 2 RadioButton WinForms objects rbtn=NULL; for(int i=0;i<2;i++) { //--- Create the RadioButton object int yrb=(rbtn==NULL ? butt.BottomEdgeRelative() : rbtn.BottomEdgeRelative()); gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,butt.CoordXRelative()-4,yrb+3,50,20,clrNONE,255,true,false); //--- get the pointer to the RadioButton object by its index in the list of bound RadioButton type objects rbtn=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,i+4); //--- If RadioButton1 is created and the pointer to it is received if(rbtn!=NULL) { //--- Set the RadioButton parameters from the EA inputs rbtn.SetAutoSize((bool)InpCheckAutoSize,false); rbtn.SetCheckAlign(InpCheckAlign); rbtn.SetTextAlign(InpCheckTextAlign); //--- Set the displayed text, frame style and color, as well as checkbox status rbtn.SetText("RadioButton"+string(i+5)); rbtn.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle); rbtn.SetBorderColor(CLR_DEF_BORDER_COLOR,true); rbtn.SetChecked(!i); rbtn.SetGroup(3); } } } } //--- Create the GroupBox2 WinForms object CGroupBox *gbox2=NULL; //--- The indent from the attached panels by 6 pixels will be the Y coordinate of GrotupBox2 w=gbox1.Width()-1; int x=gbox1.RightEdgeRelative()+1; int h=gbox1.BottomEdgeRelative()-6; //--- If the attached GroupBox object is created if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,x,2,w,h,C'0x91,0xAA,0xAE',0,true,false)) { //--- get the pointer to the GroupBox object by its index in the list of bound GroupBox type objects gbox2=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,1); if(gbox2!=NULL) { //--- set the "indented frame" type, the frame color matches the main panel background color, //--- while the text color is the background color of the last attached panel darkened by 1 gbox2.SetBorderStyle(FRAME_STYLE_STAMP); gbox2.SetBorderColor(pnl.BackgroundColor(),true); gbox2.SetForeColor(gbox2.ChangeColorLightness(obj.BackgroundColor(),-1),true); gbox2.SetText("GroupBox2"); //--- Create the CheckedListBox object gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,4,12,80,80,clrNONE,255,true,false); //--- get the pointer to the CheckedListBox object by its index in the list of bound objects of the CheckBox type CCheckedListBox *clbox=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,0); //--- If CheckedListBox is created and the pointer to it is received if(clbox!=NULL) { clbox.CreateCheckBox(4); } } } //--- Redraw all objects according to their hierarchy pnl.Redraw(true); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
The logic is described in sufficient detail in the comments to the code. You can analyze it on your own. If you have any questions, feel free to ask them in the comments below.
Compile the EA and launch it on the chart:
We see that the declared functionality works correctly. Similar objects of the same container, placed in different groups, work correctly. The objects of each group are independent units and do not affect the same-type objects from another group. Two toggle buttons combined in a group work correctly and do not affect the third button, which works independently. The CheckedListBox object in its current state also works properly. The entire visual component behaves as it should (which does not exclude the occurrence of failures in the future when changing colors when handling the previous state of the mouse and its buttons). I will find and fix all possible errors during the subsequent development of graphical elements.
What's next?
In the next article, I will continue my work on graphical elements of GUI programs created based on the library.
*Previous articles within the series:
DoEasy. Controls (Part 1): First steps
DoEasy. Controls (Part 2): Working on the CPanel class
DoEasy. Controls (Part 3): Creating bound controls
DoEasy. Controls (Part 4): Panel control, Padding and Dock parameters
DoEasy. Controls (Part 5): Base WinForms object, Panel control, AutoSize parameter
DoEasy. Controls (Part 6): Panel control, auto resizing the container to fit inner content
DoEasy. Controls (Part 7): Text label control
DoEasy. Controls (Part 8): Base WinForms objects by categories, GroupBox and CheckBox controls
DoEasy. Controls (Part 9): Re-arranging WinForms object methods, RadioButton and Button controls
DoEasy. Controls (Part 10): WinForms objects — Animating the interface
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/11194
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use