English Русский 中文 Español 日本語 Português
preview
DoEasy. Steuerung (Teil 11): WinForms Objekte — Gruppen, das WinForms-Objekt CheckedListBox

DoEasy. Steuerung (Teil 11): WinForms Objekte — Gruppen, das WinForms-Objekt CheckedListBox

MetaTrader 5Beispiele | 10 Oktober 2022, 16:44
130 0
Artyom Trishkin
Artyom Trishkin

Inhalt


Konzept

WinForms-Objekte, die an einen einzigen Container gebunden sind, werden zu einem Satz von Objekten, die zu einer einzigen Gruppe zusammengefasst werden. Unabhängig davon, ob sie an das GroupBox-Objekt oder an ein Paneel gebunden sind, wird der Container zu einer Einheit, die alle Objekte zu einer Gruppe zusammenfasst. Die Objekte beginnen, sich nach bestimmten Regeln dieser Gruppe zu verhalten. Zum Beispiel ist das RadioButton-Objekt praktisch nutzlos, wenn es alleine ist. Wenn es nicht ausgewählt ist, wird sein Kontrollkästchen nach dem Anklicken aktiviert und kann nicht wieder deaktiviert werden. Um das Kontrollkästchen zu deaktivieren, muss ein anderes Objekt desselben Typs ausgewählt werden. Hier liegt der Unterschied. Wenn wir ein anderes RadioButton-Objekt innerhalb desselben Containers auswählen, wird das erste Kontrollkästchen deaktiviert und das zweite, das angeklickt wurde, aktiviert. Wenn wir versuchen, einen RadioButton auszuwählen, der an einen anderen Container gebunden ist, wird das Ankreuzfeld des ersten ausgewählten Objekts in der ersten Gruppe nicht deaktiviert, was zu erwarten ist, da wir es mit verschiedenen Objektgruppen in verschiedenen Containern zu tun haben.

Was aber, wenn wir zwei Gruppen von RadioButton-Objekten im selben Container haben wollen, die unabhängig voneinander funktionieren? Schließlich werden sie von ihrem Container (von dem sie den Index seiner Gruppe erben sollten) zu einer Gruppe von Objekten zusammengefasst und arbeiten nach dem gemeinsamen Gruppenindex, den sie von ihrem Container erhalten haben. Um mehrere unabhängig voneinander arbeitende Gruppen solcher Objekte in einem Container zu erstellen, werde ich das Konzept der Objektgruppen einführen.

Wenn wir im Container eine Gruppe von sechs RadioButton-Objekten mit dem Gruppenindex 1 anlegen, wird allen Objekten der Gruppenindex 1 zugewiesen. Sie werden alle über den Gruppenindex ihres Containers miteinander verbunden und arbeiten entsprechend. Wenn man auf eines der sechs RadioButton-Objekte klickt, werden die übrigen fünf abgewählt.

Weisen wir aber vier Objekten der Gruppe 1 die Gruppe 2 zu, während die verbleibenden zwei Objekte die Gruppe 3 bilden, entstehen innerhalb des Containers mit der Gruppe 1 zwei Objektuntergruppen 2 und 3. Dementsprechend wird jede dieser neuen Gruppen nur in Verbindung mit den Objekten ihrer Gruppe funktionieren.

Dies ermöglicht es uns, verschiedene Untergruppen in einem Container zu erstellen, die zu einer eigenen Gruppe mit eigenem Index zusammengefasst werden, ohne dass neue Container für Gruppen innerhalb des Hauptcontainers erstellt werden müssen.

Indem wir also zwei Kippschalter zu einer Gruppe zusammenfassen, machen wir sie zu einem Schalter mit zwei Tasten, bei dem das Drücken der ersten Taste die zweite freigibt und umgekehrt. So ist es möglich, dass mehrere Umschalttasten (toggle), die in einer Gruppe zusammengefasst sind, sich entweder im Zustand befinden, in dem alle Tasten nicht gedrückt sind, oder dass nur eine von ihnen gedrückt ist, während die anderen losgelassen sind.

Außerdem werde ich die Klasse der Kontenobjekte verbessern, da der Terminalname auf einigen Servern vom Standardnamen abweichen kann. Normalerweise gibt der Server bei der Abfrage des Terminalnamens die Zeichenkette „MetaTrader 5“ zurück, aber manchmal fügen die Broker der Zeichenkette etwas anderes hinzu und ändern den Terminalnamen. Daher wäre es sinnvoll, stattdessen nach dem Teilstring „MetaTrader 5“ im Terminalnamen zu suchen, um den Servertyp zu definieren.


Verbesserung der Bibliotheksklassen

In \MQL5\Include\DoEasy\Defines.mqh, und zwar in der Enumeration der grafischen Elementtypen, fügen wir den neuen WinForms-Objekttyp hinzu:

//+------------------------------------------------------------------+
//| 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
  };
//+------------------------------------------------------------------+


In der Enumeration der ganzzahligen Eigenschaften von grafischen Elementen fügen wir eine neue Eigenschaft hinzu und erhöhen die Anzahl der ganzzahligen Objekteigenschaften von 81 auf 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
//+------------------------------------------------------------------+

Die Gruppe, zu der das grafische Element gehört, ist ein Gruppenindex, während das Schaltflächengruppen-Flag angibt, dass die Schaltfläche als Teil mehrerer Umschaltschaltflächen funktioniert. Wenn drei Schaltflächen ein Toggle-Button-Objekt bilden, sollte für jede von ihnen ein Gruppen-Button-Flag gesetzt sein, und sie sollten sich in derselben Gruppe befinden.


Wir sortieren nach neuer Eigenschaft zur Enumeration möglicher Kriterien zum Sortieren von grafischen Elementen auf der Leinwand hinzufügen:

//+------------------------------------------------------------------+
//| 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
  };
//+------------------------------------------------------------------+

Jetzt können wir grafische Elemente nach neuen Eigenschaften sortieren und auswählen.


In \MQL5\Include\DoEasy\Data.mqh wurden neuen Nachrichtenindizes hinzugefügt:

//--- 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

und die Textnachrichten, die den neu hinzugefügten Indizes entsprechen:

//--- 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"},


Ändern wir die Definition des Servertyps im Klassenkonstruktor der Datei \MQL5\Include\DoEasy\Objects\Accounts\Account.mqh der Klasse des Kontoobjektes leicht ab. Wie bereits erwähnt, wird innerhalb des Terminalnamens nach dem Teilstring „MetaTrader 5“ gesucht, anstatt den Terminalnamen abzufragen:

//+------------------------------------------------------------------+
//| 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();
  }
//+-------------------------------------------------------------------+

Den oben erhaltenen Wert (5 oder 4) fügen wir zur Variablen m_type_server hinzu. Zuvor erhielt sie das Ergebnis der Überprüfung des Terminalnamens auf den Wert „MetaTrader 5“, was manchmal zu Fehlern führte. Jetzt erhält es den Wert, der bereits in der Eigenschaft des Kontos eingetragen ist.


Machen wir in der Datei \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh der grafischen Basisobjektklasse der Bibliothek die Methoden zum Setzen und Abrufen der Objektgruppe virtuell:

//--- 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;            }

Wir müssen sie in den abgeleiteten Klassen umdefinieren.


Die grafischen Elemente der grafischen Benutzeroberfläche sind durch eine gemeinsame Hierarchie ihrer Lage zueinander miteinander verbunden. Das Objekt, an das die untergeordneten Objekte gebunden sind, dient als Basisobjekt einer Kette von verwandten Objekten. Untergeordnete Objekte können wiederum ihre eigenen Objektketten haben, die mit ihnen verbunden sind, während das Basisobjekt seinerseits ein Glied in der Kette von Objekten sein kann, die mit einem anderen verbunden sind. In diesem Fall wird das Hauptobjekt als dasjenige Objekt betrachtet, das untergeordnete Objekte hat, aber an kein anderes Objekt gebunden ist. Es ist der Elternteil der gesamten Hierarchie der Verbindungen aller untergeordneten Objekte. Typischerweise ist ein solches Objekt ein Fenster in Windows und ein Formular in C#. Hier wird es ebenfalls „Window“ sein, da die Definition von „Form“ bereits durch ein grafisches Element belegt ist, das die Funktionalität für die Arbeit mit der Maus implementiert, und dieses Objekt ist der Elternteil für das Basisobjekt WinForms.

Implementieren wir die Methoden, die die Basis- und Hauptobjekt-IDs in der Datei MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh der grafischen Elementklasse zurückgeben:

//--- 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

Verwenden wir die Ermittlung der ID des Basisobjekts: Wenn das Objekt bereits eine Basis ist (enthält untergeordnete Objekte), wird seine ID zurückgegeben. Andernfalls erhalten wir den Zeiger auf das Basisobjekt (diese Zeiger werden in jedem untergeordneten Objekt gesetzt). Wenn der Zeiger empfangen wird, wird die ID des Basisobjekts zurückgegeben, andernfalls -1 (Fehler — Objekt nicht erhalten).
Die Methodenlogik zum Abrufen der Hauptobjekt-ID ist dieselbe.


Schreiben wir die virtuellen Methoden zum Abrufen und Setzen der grafischen Elementgruppe:

//--- 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);
                       }
//+------------------------------------------------------------------+

Die Methode Group() gibt den in der Eigenschaft „group“ des Objekts festgelegten Wert zurück.

In der Methode zum Einstellen der Eigenschaft „group“ wird zunächst der Wert des übergeordneten Objekts eingestellt, der an die Methode übergeben wird. Als Nächstes setzen wir sie auf die Eigenschaft des grafischen Elements.


Jedes grafische Element in der Sammlung sollte seine eigene eindeutige ID haben. Dadurch können wir direkt auf das Objekt über seine ID verweisen (wenn es im Programm gespeichert ist) und müssen nicht in Schleifen über alle Objekte nach ihm suchen. Derzeit werden eindeutige IDs nur denjenigen grafischen Elementen zugewiesen, die direkt vom Programm aus erstellt werden. Wenn wir (auch programmatisch) neue gebundene Objekte aus bereits erstellten Objekten erstellen (die an das Objekt gebunden sind, aus dem das neue Objekt erstellt wird), dann erhält das neu erstellte untergeordnete Objekt die ID des Objekts, aus dem es erstellt wurde. In dieser Situation können wir dieses Objekt über die ID des Basisobjekts mit der Nummer des in der Liste der untergeordneten Objekte angegebenen Objekts erhalten.

Natürlich funktioniert auch dieser Ansatz, aber um die Arbeit mit Programmen zu vereinfachen, die auf der Grundlage der Bibliothek erstellt wurden, werden wir nach einer eindeutigen ID suchen und diese dem neu erstellten untergeordneten grafischen Element zuweisen. Wir werden also jedem grafischen Element eine eindeutige ID zuweisen, über die wir auf es zugreifen können. Zweitens wird die derzeitige Arbeitsmethode beibehalten, d. h. es wird auf das Basisobjekt verwiesen und das gewünschte Element aus der Liste anhand des Elementindexes abgerufen. Zwei Wege, um Zeiger auf Objekte zu erhalten, sind auf jeden Fall besser als einer, zumal es jetzt einen schnelleren Weg gibt, um auf ein Element über seine eindeutige ID zuzugreifen.

Im öffentlichen Abschnitt der Datei \MQL5\Include\DoEasy\Objects\Graph\Form.mqh der Formularobjektklasse deklarieren wir vier Methoden für die Suche nach dem Wert und der ID der maximalen Objekteigenschaft:

//--- 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

Methoden mit Parametern geben den gefundenen Höchstwert der angegebenen Eigenschaft zurück, während Methoden ohne Parameter den gefundenen Höchstwert der grafischen Element-ID zurückgeben.


Die Einstellung der ID eines neu erstellten Objekts ändern wir in der Methode zur Erstellung eines neuen angehängten Elements und dessen Hinzufügen zur Liste der gebundenen Objekte CreateAndAddNewElement().

Zuvor haben wir die ID eines Objekts festgelegt, aus dem ein neues Objekt erstellt wurde:

   obj.SetID(this.ID());

Jetzt setzen wir die ID als gefundene maximale ID aus der gesamten Hierarchie der untergeordneten Objekte, beginnend mit dem Hauptobjekt, und addieren 1 zu dem erhaltenen Wert. Mit anderen Worten: Das neue Objekt hat den größten Wert aller IDs aller Objekte in der Kollektion:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+


Nachdem der Cursor vom Bereich des grafischen Elements wegbewegt wurde, schalten wir die Ereignisbehandlung dafür ein, um die Farben des Hintergrunds, des Texts und des Rahmens des Objekts auf die Standardwerte zurückzusetzen, da sich diese Farben ändern, wenn der Cursor über den Bereich des Elements bewegt wird, um die Aktivität des grafischen Elements visuell darzustellen. Bei mehreren Tests habe ich festgestellt, dass die Farben nicht immer korrekt wiederhergestellt werden. Manchmal müssen wir den Cursor auf dem Formular, in dem sich diese Objekte befinden, neu positionieren, damit ihre Farbe auf die ursprüngliche Farbe zurückgesetzt wird. Ich werde solche Lücken bei der Entwicklung der visuellen Komponente der Bibliotheksobjekte beseitigen. Fügen wir dem letzten Mausereignis-Handler eine weitere Bedingung hinzu, die die Bewegung des Cursors vom grafischen Element weg verarbeitet:

//+------------------------------------------------------------------+
//| 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;
     }
  }
//+------------------------------------------------------------------+

Jetzt werden wir nicht nur die Situation verarbeiten, in der sich der Cursor in der aktiven Zone des Objekts befand, sondern auch die Situation, in der sich der Cursor in der Fläche des Objekts befand, da die aktive Zone nicht immer die Abmessungen der gesamten Fläche des Elements hat und der Cursor, bevor er das Formular aus der aktiven Zone verlässt, in die inaktive Zone fällt, aber innerhalb des grafischen Elements. Nun wird auch dieser Situation Rechnung getragen.


Die Methode, die das Maximum der angegebenen Integer-Eigenschaft aller Objekte zurückgibt, die dem Basisobjekt untergeordnet sind:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Die Methodenlogik wurde in den Codekommentaren ausführlich beschrieben. Diese Methode sucht nach dem maximalen Wert der angegebenen Integer-Eigenschaft, der den Wert Null oder größer (nicht negativ) aller an das Formular gebundenen Objekte hat. In diesem Fall wird der Wert der Eigenschaft des Basisobjekts berücksichtigt, und die Suche beginnt damit. Da alle untergeordneten Objekte nacheinander aus dem Basisobjekt erstellt werden, kann es nicht vorkommen, dass wir das Maximum der Eigenschaft in den Objekten, die direkt mit der Basis verbunden sind, nicht finden und die Suche in einem Objekt beginnen, das in der Hierarchie weit von der Basis entfernt ist.
Wenn wir jedoch einmal die gesamte Hierarchie des Basisobjekts genau durchsuchen müssen, können wir problemlos eine solche Methode erstellen.


Die Methode, die das Maximum der ID des grafischen Elements von allen Objekten, die an das Basisobjekt gebunden sind, zurückgibt:

//+------------------------------------------------------------------+
//| Returns the maximum value of an ID                               |
//| from all bound objects                                           |
//+------------------------------------------------------------------+
int CForm::GetMaxIDForm(void)
  {
   return (int)this.GetMaxLongPropForm(CANV_ELEMENT_PROP_ID);
  }
//+------------------------------------------------------------------+

Die Methode gibt einfach das Ergebnis der obigen Methode zurück, an die die Eigenschaft „Objekt-ID“ zur Suche übergeben wird.


Die Methode liefert das Maximum der angegebenen Integer-Eigenschaft aus der gesamten Hierarchie der verwandten Objekte:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Die Logik der Methode ist in den Kommentaren zum Code detailliert beschrieben und ähnelt der Methode zur Ermittlung der maximalen Eigenschaft von Objekten, die mit dem Basisobjekt verbunden sind. Anders als bei der ersten Methode beginnen wir die Suche beim Hauptobjekt — dem Vorfahren der Hierarchie der verwandten Objekte — und gehen die Listen aller Objekte der gesamten hierarchischen Kette verwandter Objekte durch. Als Ergebnis haben wir den größten Eigenschaftswert der gesamten Hierarchie der untergeordneten Objekte.


Die Methode liefert das Maximum der ID aus der gesamten Hierarchie der verwandten Objekte:

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Die Methode gibt das Ergebnis des Aufrufs der obigen Methode zurück. In den Parametern dieser Methode wird die Eigenschaft „ID“ übergeben, um nach ihrem Höchstwert zu suchen.


Die Beschreibungen der Eigenschaften grafischer Elemente sind in der Klasse des WinForms-Basisobjekts in \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh implementiert.

Fügen Sie in der Methode GetPropertyDescription() Codeblöcke für die Rückgabe der Beschreibung von zwei neuen Grafikelement-Eigenschaften hinzu:

      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)
         )  :

Jetzt gibt die Methode die Beschreibungen aller grafischen Elementeigenschaften korrekt zurück.


Das WinForms-Objekt RadioButton kann nur in Verbindung mit anderen Objekten dieses Typs korrekt funktionieren. Außerdem sollten diese Objekte entweder an denselben Container gebunden sein oder denselben Gruppenwert haben (zur selben Gruppe von Objekten gehören). Wenn eines dieser Objekte angeklickt wird, wird sein Kontrollkästchen aktiviert (wenn es vorher nicht aktiviert war), und die Kontrollkästchen aller anderen Objekte dieser Gruppe werden deaktiviert. Wenn man erneut auf ein bereits ausgewähltes Objekt klickt, wird dessen Markierung nicht gelöscht.

Nehmen wir nun die Verbesserungen an der RadioButton-Objektklasse in \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\RadioButton.mqh vor.

Im privaten Abschnitt der Klasse deklarieren wir die Methode, die den Status „nicht ausgewählt“ für alle Objekte derselben Gruppe festlegt. Im öffentlichen Abschnitt implementieren wir die Methode, die das angegebene Objekt und seinen Checkbox-Status setzt:

//+------------------------------------------------------------------+
//| 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 der Methode zum Setzen des Status des Kontrollkästchens implementieren wir den an die Methode übergebenen Wert in dessen Eigenschaft und setzen dann den Auswahlstatus (entweder ausgewählt oder nicht). Wenn der Zustand des Objekts „ausgewählt“ ist, rufen wir die Methode auf, in der alle anderen ähnlichen Objekte dieser Gruppe auf „nicht ausgewählt“ gesetzt werden, und das Kontrollkästchen wird deaktiviert.


In der Ereignisbehandlung von „Der Cursor befindet sich im aktiven Bereich, die linke Maustaste wurde geklickt“ fügen wir den Codeblock hinzu, in dem der Objektstatus überprüft wird. Wenn er nicht ausgewählt ist, rufen wir die Methode zum Setzen des Objekts auf „ausgewähltauf:

//+------------------------------------------------------------------+
//| '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);
  }
//+------------------------------------------------------------------+

Hier wird ein Debug-Eintrag im Protokoll angezeigt, der das Ereignis, den Zustand des Elements (ausgewählt/nicht ausgewählt), seine ID und den Gruppenindex angibt. Später werde ich diesen Eintrag entfernen und ihn durch das Senden einer Nachricht an die Bibliothek und das Steuerprogramm ersetzen.


Die Methode, die den Zustand des Ankreuzfeldes für alle RadioButton-Objekte der gleichen Gruppe im Container auf „nicht ausgewählt“ setzt:

//+------------------------------------------------------------------+
//| 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);
        }
     }
  }
//+------------------------------------------------------------------+

Jede Zeile des Codes ist kommentiert. Ich hoffe, die Logik der Methode wird keine Fragen aufwerfen. Kurz gesagt, wir müssen eine Liste aller RadioButton-Objekte erhalten, die an den Container gebunden sind. Sie sollten alle zur gleichen Gruppe gehören, und die Liste sollte nicht das Objekt enthalten, von dem aus die Methode aufgerufen wurde (schließlich ist dies das Objekt, das mit der Maus angeklickt und ausgewählt wurde, was bedeutet, dass wir die Auswahlmarkierung nicht entfernen müssen). Durch die resultierende Liste gehen wir mit einer Schleife und setzen jedes der Objekte in einen nicht ausgewählten Zustand und deaktivieren das Kontrollkästchen. Die Objekte werden neu gezeichnet, um die Änderungen widerzuspiegeln.


In der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh sollten ähnliche Verbesserungen vorgenommen werden, sodass die Schaltflächen nicht nur anklickbar sind, sondern auch den Status aktiviert/deaktiviert haben können. Dementsprechend können wir sie Gruppen zuordnen, in denen Tasten, die mit einer Gruppe verbunden sind, gemeinsam funktionieren.

Im privaten Abschnitt der Klasse deklarieren wir die Methode, die den Status „freigegeben“ (released) für alle Schaltflächen derselben Gruppe festlegt:

//+------------------------------------------------------------------+
//| 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:


Im öffentlichen Abschnitt der Klasse ändern wir die Methode zum Setzen des Schaltflächenstatus und implementieren die Methoden zum Setzen und Zurückgeben des Flags einer Schaltfläche, die in einer Gruppe mit anderen Schaltflächenobjekten arbeitet:

//--- (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 der Methode, die den Zustand der Schaltfläche festlegt, wird der Zustand zuerst in der Objekteigenschaft festgelegt, und dann, wenn der Zustand „gedrückt“ ist, rufen wir die Methode auf, die den Zustand der übrigen Schaltflächen derselben Gruppe auf „freigegeben“ festlegt.


In der letzten Ereignisbehandlung der Maus fügen wir eine weitere Bedingung hinzu, die der oben beschriebenen gleichnamigen Methode der Formularobjektklasse ähnelt:

//+------------------------------------------------------------------+
//| 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;
     }
  }
//+------------------------------------------------------------------+


Die Methode, die den Schaltflächenstatus für alle Schaltflächenobjekte derselben Gruppe im Container auf „freigegeben“ setzt:

//+------------------------------------------------------------------+
//| 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);
        }
     }
  }
//+------------------------------------------------------------------+

Die Methodenlogik ist identisch mit der Methode der Objektklasse RadioButton. Sie ist in den Kommentaren zum Code ausführlich beschrieben und bedarf hoffentlich keiner weiteren Erläuterung.


Im Normalzustand ändert das WinForms-Objekt CheckBox die Farbe des Hintergrunds, des Kontrollkästchens und des Rahmens des Kontrollkästchens, wenn man mit dem Mauszeiger darüber fährt. Der Hintergrund des Objekts selbst bleibt unverändert (transparent). Wenn solche Objekte jedoch zu einer Gruppe zusammengefasst sind (wie im Fall des nächsten Objekts), ändert sich auch die Hintergrundfarbe, wenn man mit dem Mauszeiger über das Objekt fährt. Um das CheckBox-Objekt zu verwenden, um eine Objektliste mit CheckBox-Objekten zu erstellen, werden wir Änderungen an diesem Objekt in \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh vornehmen.

Machen wir die Methode zum Setzen des Status des Kontrollkästchens virtuell, genau wie in seinem RadioButton-Unterobjekt:

//--- (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);                            }


Die Hintergrundfarbe des Objekts setzen wir im Klassenkonstruktor auf volle Deckkraft und stellen die Verschiebung des aktiven Bereichs um ein Pixel auf jeder Seite ein:

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Hier setzen wir den Hintergrund auf volle Deckkraft, da wir weitere Objekte mit der Deckkraft des Basisobjekts erstellen müssen. Um zu vermeiden, dass die Deckkraft dieses Objekts bei seiner Erstellung ständig neu eingestellt werden muss, wird sie hier im Konstruktor explizit eingestellt. Die Verschiebung des aktiven Bereichs um ein Pixel auf jeder Seite ist ein Versuch, den Abstand zwischen benachbarten CheckBox-Objekten zu vergrößern, sodass der Cursor, wenn er sich von einem Objekt wegbewegt, Zeit hat, das Objekt zu „besuchen“, ohne sofort über dem nächsten Objekt zu schweben - sodass der Cursor durch das Basisobjekt hindurchgeht, ohne sofort das benachbarte Objekt zu treffen. All dies ist das Ergebnis einer Suche nach einer Lösung für das Problem, dass nahe gelegene Objekte nicht immer ihre Standard-Hintergrundfarbe wiederherstellen, wenn man den Cursor von ihnen wegbewegt. Eine solche Lösung ist jedoch nicht immer hilfreich. Ich muss noch die Zeit finden, um das Problem gründlich zu erfassen und zu beheben.

In der Methode zum Neuzeichnen eines Objekts wird nun der in den Objekteigenschaften angegebene Wert für die Deckkraft angegeben, anstatt die volle Deckkraft (den Wert 0) zu setzen:

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

So können wir die Hintergrundfarbe verwenden, um sie zu ändern, wenn wir mit der Maus über dem Objekt verweilen. Bei einem undurchsichtigen Hintergrund (wie zuvor) können natürlich keine Änderungen der Hintergrundfarbe angezeigt werden.


Implementieren wir die notwendigen Änderungen in allen Ereignisbehandlungen der Maus, die die Änderung der Hintergrundfarbe des Objekts erfordern:

//+------------------------------------------------------------------+
//| '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);
  }
//+------------------------------------------------------------------+


Die Überprüfung des bereits bekannten Status fügen wir zum letzten Mausereignisbehandlung hinzu:

//+------------------------------------------------------------------+
//| 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;
     }
  }
//+------------------------------------------------------------------+

Jetzt können wir mit der Erstellung eines neuen Objekts beginnen.


Das WinForms-Objekt CheckedListBox

Dieses WinForms-Objekt ist ein Paneel, das die Liste der CheckBox-Objekte enthält. Wenn wir mit dem Mauszeiger über die Listenobjekte fahren, ändert sich deren Hintergrundfarbe zusammen mit der Farbe des Ankreuzfeldes, seines Hintergrundes und des Randes des Ankreuzfeldes. Die Objekte der Liste können sowohl vertikal übereinander als auch in Spalten mit mehreren Stücken angeordnet werden. Heute werden wir nur die vertikale Anordnung der Objekte vornehmen.

Im Bibliotheksverzeichnis \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ erstellen wir die neue Datei CheckedListBox.mqh der Klasse CCheckedListBox.

Die Klasse wird nun vom Basis-Containerobjekt abgeleitet: Damit es sowohl CContainer- als auch CCheckBox-Klassen „sieht“, binden wir die Klassendatei des Paneel-Objekts ein, in der alle erforderlichen Klassendateien bereits sichtbar sind:

//+------------------------------------------------------------------+
//|                                               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
  {
  }


Im privaten Abschnitt deklarieren wir die virtuelle Methode zur Erstellung eines neuen grafischen Objekts, während wir im öffentlichen Abschnitt die Methode zur Erstellung der Liste deklarieren, die aus der angegebenen Anzahl von CheckBox-Objekten besteht sowie den parametrischen Konstruktor :

//+------------------------------------------------------------------+
//| 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);
  };
//+------------------------------------------------------------------+


Der parametrische Konstruktor:

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Wir bestimmen den Typ des WinForms-Objekts und den grafischen Bibliotheksobjekttyp für das Objekt. Als Nächstes stellen wir die Größe des Objektrahmens auf ein Pixel ein, der Rahmentyp ist einfach. Außerdem legen wir die Standardrahmen- und Textfarbe für grafische Objekte in der Bibliothek fest.


Die Methode, die die angegebene Anzahl von CheckBox-Objekten im Hauptfenster erstellt:

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Die Methodenlogik wird im Code ausführlich kommentiert. Kurz gesagt, die erforderliche Anzahl von CheckBox-Objekten, die auf dem Paneel erstellt werden sollen, wird an die Methode übergeben. In der Schleife durch die angegebene Anzahl von Objekten, erstellen wir sie und legen die erforderlichen Eigenschaften für sie. Nach Abschluss der Schleife zur Erstellung von CheckBox-Objekten stellen wir den Modus zur automatischen Größenanpassung des Bedienfelds ein, damit es an die Gesamtgröße aller darin erstellten Objekte angepasst werden kann. Außerdem setzen wir das Flag für die automatische Größenanpassung des Bedienfelds, was wiederum dazu führt, dass die Größe des Bedienfelds an die darin erstellten Objekte angepasst wird.

Die virtuelle Methode zur Erstellung eines neuen grafischen Objekts:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Da eines der übergeordneten Objekte die Klasse des Formularobjekts ist, das die Funktionalität für die Arbeit mit der Maus bietet, und die Klasse dieses Objekts darin nicht sichtbar ist, müssen wir hier die virtuelle Methode der Klasse CForm überschreiben, um ein neues grafisches Objekt zu erstellen. Bei dieser Methode brauchen wir den Typ eines an die Methode übergebenen Objekts nicht zu prüfen, da wir hier genau wissen, welcher Typ von Objekt erstellt werden soll. Bei diesem Typ handelt es sich um ein Kontrollkästchen (CheckBox), das wir hier erstellen und für das wir die Mindestwerte festlegen — das Verschub-Flag und die relativen Koordinaten.

Alle anderen Methoden der Klasse sind bereits in den übergeordneten Klassen enthalten.

Natürlich werde ich die Objektklasse später verfeinern, um ihre zusätzlichen Funktionen zu implementieren.

Jetzt müssen wir die Behandlung dieses Objekttyps in allen Containerklassen hinzufügen, damit wir solche Objekte in ihnen erstellen können.

In der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh der Paneel-Objektklasse binden wir die Datei der neu erstellten Klasse ein:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Container.mqh"
#include "GroupBox.mqh"
#include "..\..\WForms\Common Controls\CheckedListBox.mqh"
//+------------------------------------------------------------------+
//| Panel object class of WForms controls                            |
//+------------------------------------------------------------------+

Diese neue Klasse wird nun in allen Containerklassen der Bibliothek sichtbar sein.


In der Methode, die ein neues grafisches Objekt erstellt, fügen wir den Typ des neuen Bibliotheksobjekts hinzu:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Hier erstellen wir einfach ein neues Objekt der Klasse CCheckedListBox.

In der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh der Container-Basisklasse, und zwar in der Methode, die ein neues gebundenes Element erstellt, setzen wir für das erstellte Objekt die Gruppe, die der des Basisobjekts entspricht, aber nur, wenn das erstellte Objekt kein Containerobjekt ist. Wir fügen der Einstellung einer transparenten Hintergrundfarbe und ihrer vollen Transparenz hinzu sowie die Behandlung des WinForms-Objekts CheckedListBox für die WinForms-Objekte „Text label“, „CheckBox“ und „RadioButton“:

//+------------------------------------------------------------------+
//| 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 der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh der Objektklasse GroupBox, und zwar in der Methode zur Erstellung eines neuen grafischen Objekts, fügen wir die Behandlung des neuen Typs des Objekts CheckedListBox hinzu:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+


Ganz am Ende der Variableninitialisierungsmethode Initialize() fügen wir das Einstellen des Standardgruppenwerts hinzu:

   this.SetEnabled(true);
   this.SetVisible(true,false);
//--- Set the default group value
   this.SetGroup((int)this.GetMaxLongPropAll(CANV_ELEMENT_PROP_GROUP)+1);
  }
//+------------------------------------------------------------------+

Hier rufen wir die zuvor betrachtete Methode auf, die den maximalen Wert der angegebenen Eigenschaft von allen Objekten in der Sammlung der grafischen Elemente der Bibliothek zurückgibt. Wir geben die Eigenschaft „Gruppe“ als gewünschten Parameter an und addieren 1 zum resultierenden Wert, wodurch der Wert der neuen Gruppe auf Maximum gesetzt wird. Es ist wichtig, eine eindeutige Gruppe zu haben, die sie von anderen Containerobjekten unterscheidet.


In der Klassendatei \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh für grafische Elementsammlungen fügen wir die Datei der Objektklasse CheckedListBox zur Liste der Include-Dateien hinzu:

//+------------------------------------------------------------------+
//| 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"


Im privaten Abschnitt der Klasse deklarieren wir die Methode, die die maximale ID aller grafischen Elemente der Kollektion zurückgibt:

//--- 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 allen Methoden zur Erstellung grafischer Elemente ersetzen wir die Zeichenfolge, die die Gesamtzahl der grafischen Elemente in der Kollektion als ID angibt,

int id=this.m_list_all_canv_elm_obj.Total();

mit der Zeile, die die maximale ID aller grafischen Elemente der Sammlung plus 1 zuweist:

//--- 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

Solche Änderungen wurden in allen Methoden vorgenommen, die grafische Elemente erstellen. Ich werde sie hier nicht aufführen. Alle diese Änderungen sind in den Bibliotheksdateien im Anhang zu diesem Artikel zu finden.

In der Methode, die das ehemals aktive Formular unter dem Cursor behandelt, fügen wir den Aufruf der Maus-Ereignisbehandlung für das aktuelle Schleifenobjekt hinzu, um zu vermeiden, dass dessen Behandlung übersprungen wird, falls das Objekt noch nicht in der Liste der inaktiven Objekte steht, wenn der Cursor davon wegbewegt wird, aber in Wirklichkeit bereits inaktiv ist:

//+------------------------------------------------------------------+
//| 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();
        }
     }
  }
//+------------------------------------------------------------------+


Um die Handhabung der Interaktion der rechten Maustaste mit den GUI-Objekten des Programms zu implementieren, fügen wir die Prüfung für das Drücken und Halten der rechten Maustaste in der Ereignisbehandlung OnChartEvent() ein:

//--- 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

Der Zustand der Maustasten wird in eine Variable gesetzt, sodass wir später, wenn wir diesen Wert benötigen, die Methode, die den Zustand der Tasten ausliest, nicht erneut aufrufen müssen.


Die Methode, die die maximale ID von allen grafischen Elementen in der Sammlung zurückgibt:

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Hier erhalten wir den Index des Objekts in der Sammlungsliste mit dem höchsten ID-Wert. Wir erhalten den Zeiger auf ein Objekt durch einen empfangenen Index und geben die Objekt-ID zurück, wenn der Zeiger auf das Objekt empfangen wurde. Andernfalls wird -1 zurückgegeben. Entweder ist die Kollektion der grafischen Elemente leer, oder es ist ein Fehler beim Abrufen des Zeigers aufgetreten.


Test

Um den Test durchzuführen, verwenden wir den EA aus dem vorherigen Artikel und speichern ihn in \MQL5\Experts\TestDoEasy\Part111\ als TstDE111.mq5.

Vergrößern wir das Hauptpaneel und platzieren das CheckBox-Objekt in seinem ersten Container-Objekt zusammen mit vier RadioButton-Objekten mit dem Gruppenwert 2, drei Button-Objekten, von denen zwei die Gruppe 2 haben werden, während das dritte zur Gruppe 1 gehört, die standardmäßig von seiner Container-Objektgruppe geerbt wird. Unterhalb der Schaltflächen platzieren wir zwei weitere RadioButton-Objekte mit dem Gruppenwert 3. Im Container befinden sich also vier RadioButton-Objekte mit der Gruppe 2, zwei RadioButton-Objekte mit der Gruppe 3 und drei Schaltflächen — zwei mit der Gruppe 2 und eine mit der Gruppe 1.

Wir platzieren rechts neben dem ersten GroupBox-Container einen weiteren Container desselben Typs und erstellen darin ein neues CheckedListBox-Objekt. Das Objekt wird verwendet, um vier CheckBox-Objekte zu erstellen. Alle Objekte, die sich in verschiedenen Gruppen desselben Containers befinden, sollten als separate Objektgruppen funktionieren. Die gesamte visuelle Komponente der Interaktion von Objekten mit der Maus sollte gut funktionieren.

In OnInit() des EAs platzieren wir den folgenden Codeblock, um alle GUI-Elemente zu erstellen:

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Die gesamte Logik ist in den Code-Kommentaren ausreichend detailliert beschrieben. Sie können sie selbst analysieren. Wenn Sie Fragen haben, können Sie diese gerne im Kommentarteil stellen.

Kompilieren Sie den EA und starten Sie ihn auf einem Chart:


Wir sehen, dass die angegebene Funktionen korrekt funktionieren. Ähnliche Objekte desselben Container, die in verschiedenen Gruppen angeordnet sind, funktionieren korrekt. Die Objekte jeder Gruppe sind unabhängige Einheiten und beeinflussen die gleichartigen Objekte einer anderen Gruppe nicht. Zwei Umschalttasten, die in einer Gruppe zusammengefasst sind, funktionieren korrekt und haben keinen Einfluss auf die dritte Taste, die unabhängig davon funktioniert. Das Objekt CheckedListBox in seinem aktuellen Zustand funktioniert ebenfalls ordnungsgemäß. Die gesamte visuelle Komponente verhält sich so, wie sie sollte (was das Auftreten von Fehlern in der Zukunft nicht ausschließt, wenn sich die Farben beim Umgang mit dem vorherigen Zustand der Maus und ihrer Tasten ändern). Ich werde alle möglichen Fehler bei der späteren Entwicklung der grafischen Elemente finden und beheben.


Was kommt als Nächstes?

Im nächsten Artikel werde ich meine Arbeit an grafischen Elementen von GUI-Programmen fortsetzen, die auf der Grundlage der Bibliothek erstellt wurden.

Alle Dateien der aktuellen Bibliotheksversion, des Test-EA und des Chartereignis-Kontrollindikators für MQL5 sind unten angehängt, damit Sie sie testen und herunterladen können. Schreiben Sie Ihre Fragen, Kommentare und Vorschläge im Kommentarteil.

Zurück zum Inhalt

*Vorherige Artikel in dieser Reihe:

DoEasy. Steuerung (Teil 1): Erste Schritte
DoEasy. Steuerung (Teil 2): Arbeiten an der Klasse CPanel
DoEasy. Steuerung (Teil 3): Erstellen gebundener Steuerelemente
DoEasy. Steuerung (Teil 4): Paneel-Steuerung, Parameter für Padding und Dock
DoEasy. Steuerung (Teil 5): Basisobjekt von WinForms, Paneel-Steuerelement, Parameter AutoSize
DoEasy. Steuerung (Teil 6): Paneel-Steuerung, automatische Größenanpassung des Containers an den inneren Inhalt
DoEasy. Steuerung (Teil 7): Steuerung der Text Label
DoEasy. Steuerung (Teil 8): Objektkategorien von Basis-WinForms zur Steuerung von GroupBox- und CheckBox
DoEasy. Steuerung (Teil 9): Neuanordnung von WinForms-Objektmethoden, Steuerung von RadioButton und Steuerungen
DoEasy. Steuerung (Teil 10): WinForms-Objekte — Animieren der Nutzeroberfläche



Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/11194

Beigefügte Dateien |
MQL5.zip (4447.61 KB)
Neuronale Netze leicht gemacht (Teil 21): Variierter Autoencoder (VAE) Neuronale Netze leicht gemacht (Teil 21): Variierter Autoencoder (VAE)
Im letzten Artikel haben wir uns mit dem Algorithmus des Autoencoders vertraut gemacht. Wie jeder andere Algorithmus hat auch dieser seine Vor- und Nachteile. In seiner ursprünglichen Implementierung wird der Autoencoder verwendet, um die Objekte so weit wie möglich von der Trainingsstichprobe zu trennen. Dieses Mal werden wir darüber sprechen, wie man mit einigen ihrer Nachteile umgehen kann.
Experimente mit neuronalen Netzen (Teil 2): Intelligente Optimierung neuronaler Netze Experimente mit neuronalen Netzen (Teil 2): Intelligente Optimierung neuronaler Netze
In diesem Artikel werde ich mit Hilfe von Experimenten und unkonventionellen Ansätzen ein profitables Handelssystem entwickeln und prüfen, ob neuronale Netze für Händler eine Hilfe sein können. Der MetaTrader 5 als ein autarkes Tool für den Einsatz neuronaler Netze im Handel.
DoEasy. Steuerung (Teil 12): WinForms-Objekte Basislistenobjekt, ListBox und ButtonListBox DoEasy. Steuerung (Teil 12): WinForms-Objekte Basislistenobjekt, ListBox und ButtonListBox
In diesem Artikel werde ich das Basisobjekt der WinForms-Objektlisten sowie die beiden neuen Objekte erstellen: ListBox und ButtonListBox.
Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 23): Neues Auftragssystems (VI) Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 23): Neues Auftragssystems (VI)
Wir werden das Auftragssystem flexibler gestalten. Hier werden wir Änderungen am Code in Erwägung ziehen, die ihn flexibler machen, sodass wir die Positionsstopp-Levels viel schneller ändern können.