English Русский 中文 Español 日本語 Português
preview
DoEasy. Steuerung (Teil 13): Optimierung der Interaktion von WinForms-Objekten mit der Maus, Beginn der Entwicklung des WinForms-Objekts TabControl

DoEasy. Steuerung (Teil 13): Optimierung der Interaktion von WinForms-Objekten mit der Maus, Beginn der Entwicklung des WinForms-Objekts TabControl

MetaTrader 5Beispiele | 20 Oktober 2022, 17:24
107 0
Artyom Trishkin
Artyom Trishkin

Inhalt


Konzept

Die grafischen Objekte der Bibliothek zur Erstellung von GUI-Steuerelementen haben derzeit einen Nachteil: Wenn man mit der Maus über einige Objekte fährt, ändern sie ihr Aussehen, aber wenn man den Mauszeiger vom Objektbereich wegbewegt, stellt sich der ursprünglichen Zustand nicht wieder her. Dies geschieht, wenn sich zwei Objekte nahe beieinander befinden. Auch die Richtung, in der sich der Cursor vom Objekt entfernt, wirkt sich darauf aus. Wenn die Bewegung von unten nach oben oder von links nach rechts erfolgt, reagieren die Objekte korrekt auf den Cursor. Im Falle der entgegengesetzten Richtungen stellen grafische Objekte ihren ursprünglichen Zustand nicht wieder her, wenn der Cursor aus dem Objektbereich herausbewegt wird. Sobald wir jedoch die Objekte in einem größeren Abstand zueinander platzieren, beginnen sie sich korrekt zu verhalten.

Dieses Problem hat uns daran gehindert, einige Objekte zu erstellen. Wir mussten ihre Komponenten in einem größeren Abstand zueinander platzieren als die Komponenten ähnlicher Objekte aus dem MS Visual Studio Control Set. Bei der Analyse dieses Verhaltens kam ich zu dem Schluss, dass ein Objekt erst in den Formularbereich eintreten muss, in dem sich die grafischen Objekte befinden, damit es sein ursprüngliches Aussehen wieder annimmt. Dadurch werden sie gezwungen, sich in einem gewissen Abstand voneinander zu befinden (mindestens 4 Pixel).

Ich werde dieses Problem im aktuellen Artikel beheben. Darüber hinaus werde ich auch die Umschaltung der Farbe von grafischen Objekten optimieren, wenn diese mit der Maus interagieren. Außerdem werde ich mit der Entwicklung des grafischen Objekts TabControl beginnen, das aus einer Reihe von Registerkarten besteht, auf denen man verschiedene WinForms-Objekte platzieren kann. Leider bin ich bei der Entwicklung dieses Steuerelements auf einen ebenso kritischen Fehler bei der Planung der Bibliotheksstruktur gestoßen, nämlich die Logik für die Erstellung der Namen der grafischen Objekte. Unser Name des grafischen Objekts besteht derzeit aus dem Namen des Programms mit dem Zusatz des Namens des Panels zusammen mit den Endungen „_Elm00“, „_Elm01“, „_Elm02“.

Wenn das Programm beispielsweise „Programm“ heißt und drei leere Felder im Chart erstellt werden, lauten die Namen dieser drei Felder „Programm_WFPanel1“, „Programm_WFPanel2“ und „Programm_WFPanel3“. In diesem Fall werden die Namen der Panel-Objekte vom Nutzer der Bibliothek vergeben, der den Namen des erstellten Panels aus seinem Programm erstellt. In unserem Fall sind dies WFPanel1, WFPanel2 und WFPanel3. Wenn wir ein anderes Objekt an das erste Panel anhängen, erstellt die Bibliothek automatisch einen Namen dafür, und der lautet wie folgt: Programm_WFPanel1_Elm00.

Wenn man ein angefügtes Element innerhalb eines anderen angefügten Elements erstellt, wird der Name noch länger: Programm_WFPanel1_Elm00_Elm00. Wenn wir ein weiteres Element auf dem ersten angehängten Element erstellen, wird sein Name WFPanel1_Elm00_Elm01 sein. Nach der Erstellung eines weiteren wird der Name WFPanel1_Elm00_Elm02 lauten und so weiter. Wenn wir also für jedes angehängte Objekt neue angehängte Objekte erstellen, wird der Name länger, da die gesamte Hierarchie aller angehängten Objekte darin geschrieben wird. Der Name der Grafikressource darf nicht länger als 63 Zeichen sein. In der Standardbibliotheksklasse CCanvas wird eine grafische Ressource erstellt, deren Name nutzerspezifisch (bibliotheksgeneriert) plus die Anzahl der Millisekunden seit dem Systemstart plus eine pseudozufällige Ganzzahl im Bereich von 0 bis 32767 (in der Methode Create) ist:

m_rcname="::"+name+(string)ChartID()+(string)(GetTickCount()+MathRand());

Es bleiben also nur sehr wenige freie Zeichen für den Namen eines im Programm angegebenen und von der Bibliothek erzeugten Objekts.

Daher ist das früher angewandte Konzept, die Namen der grafischen Elemente der Bibliothek zu konstruieren, nicht mehr praktikabel. Dies ist mir bereits bei der Entwicklung des grafischen Elements TabControl aufgefallen. Es ist nicht mehr möglich, andere grafische Elemente an die Registerkarten anzuhängen. Der Name der grafischen Ressource wird zu lang und die Methode Create() der Klasse CCanvas gibt false zurück.

In diesem Artikel werde ich die notwendige Basis für die Erstellung des WinForm-Objekts TabControl vorbereiten, Klassenrohlinge für seine vollwertige Erstellung erstellen, sie aber hier nicht weiter entwickeln. Stattdessen werde ich ein Layout dieses Steuerelements aus „improvisierten Werkzeugen“ erstellen, das ich verwenden werde, um die künftige Funktionalität und das erforderliche Aussehen dieses Objekts zu prüfen. Im nächsten Artikel werde ich eine neue Mechanik für die Erstellung der Namen der grafischen Elemente der Bibliothek implementieren und damit beginnen, ein vollwertiges TabControl WinForms-Objekt zu erstellen, indem ich die hier für seine Erstellung vorbereiteten Klassen und die bei der Entwicklung des Layouts gewonnenen Erfahrungen verwende.


Verbesserung der Bibliotheksklassen

Wenn man den Mauszeiger über das TabPage-Objekt bewegt oder auf den Titel einer Registerkarte klickt, sollten sich die Elemente des Objekts auf eine bestimmte Weise verhalten — einige sollten ihre Farbe ändern, während der Titel der Registerkarte sich leicht vergrößern sollte, um anzuzeigen, dass sie ausgewählt wurde. Die Rahmen der Objektelemente erhalten ihre eigenen Farben, die dem Zustand des Objekts entsprechen, wenn es ausgewählt ist oder der Mauszeiger über ihm schwebt. Für alle diese möglichen Zustände definieren wir Makro-Substitutionen, die die Standard-Farbwerte der verschiedenen Modi der Objektkomponenten speichern.

Legen wir in \MQL5\Include\DoEasy\Defines.mqh die folgenden Makrosubstitutionen fest:

#define CLR_DEF_CONTROL_STD_BACK_COLOR_ON (C'0xC9,0xDE,0xD0')     // Background color of standard controls which are on
#define CLR_DEF_CONTROL_STD_BACK_DOWN_ON (C'0xA6,0xC8,0xB0')      // Color of standard control background when clicking on the control when it is on
#define CLR_DEF_CONTROL_STD_BACK_OVER_ON (C'0xB8,0xD3,0xC0')      // Color of standard control background when hovering the mouse over the control when it is on

#define CLR_DEF_CONTROL_TAB_BACK_COLOR (CLR_CANV_NULL)            // TabControl background color
#define CLR_DEF_CONTROL_TAB_MOUSE_DOWN (CLR_CANV_NULL)            // Color of TabControl background when clicking on the control
#define CLR_DEF_CONTROL_TAB_MOUSE_OVER (CLR_CANV_NULL)            // Color of TabControl background when hovering the mouse over the control
#define CLR_DEF_CONTROL_TAB_OPACITY    (0)                        // TabControl background opacity

#define CLR_DEF_CONTROL_TAB_BACK_COLOR_ON (CLR_CANV_NULL)         // Enabled TabControl background color
#define CLR_DEF_CONTROL_TAB_BACK_DOWN_ON (CLR_CANV_NULL)          // Color of enabled TabControl background when clicking on the control
#define CLR_DEF_CONTROL_TAB_BACK_OVER_ON (CLR_CANV_NULL)          // Color of enabled TabControl background when hovering the mouse over the control

#define CLR_DEF_CONTROL_TAB_BORDER_COLOR (CLR_CANV_NULL)          // TabControl frame color
#define CLR_DEF_CONTROL_TAB_BORDER_MOUSE_DOWN (CLR_CANV_NULL)     // Color of TabControl frame when clicking on the control
#define CLR_DEF_CONTROL_TAB_BORDER_MOUSE_OVER (CLR_CANV_NULL)     // Color of TabControl frame when hovering the mouse over the control

#define CLR_DEF_CONTROL_TAB_BORDER_COLOR_ON (CLR_CANV_NULL)       // Enabled TabControl frame color
#define CLR_DEF_CONTROL_TAB_BORDER_DOWN_ON (CLR_CANV_NULL)        // Color of enabled TabControl frame when clicking on the control
#define CLR_DEF_CONTROL_TAB_BORDER_OVER_ON (CLR_CANV_NULL)        // Color of enabled TabControl frame when hovering the mouse over the control

#define CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR (C'0xFF,0xFF,0xFF')   // TabPage control background color
#define CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN (C'0xFF,0xFF,0xFF')   // Color of TabPage control background when clicking on the control
#define CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER (C'0xFF,0xFF,0xFF')   // Color of TabPage control background when hovering the mouse over the control
#define CLR_DEF_CONTROL_TAB_PAGE_OPACITY    (255)                 // TabPage background opacity

#define CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR_ON (C'0xFF,0xFF,0xFF')// Color of the enabled TabPage control background
#define CLR_DEF_CONTROL_TAB_PAGE_BACK_DOWN_ON (C'0xFF,0xFF,0xFF') // Color of the enabled TabPage control background when clicking on the control
#define CLR_DEF_CONTROL_TAB_PAGE_BACK_OVER_ON (C'0xFF,0xFF,0xFF') // Color of the enabled TabPage control background when hovering the mouse over the control

#define CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR (C'0xDD,0xDD,0xDD') // TabPage control frame color
#define CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN (C'0xDD,0xDD,0xDD') // Color of TabPage control background frame when clicking on the control
#define CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER (C'0xDD,0xDD,0xDD') // Color of TabPage control background frame when hovering the mouse over the control

#define CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR_ON (C'0xDD,0xDD,0xDD')// Color of the enabled TabPage control frame
#define CLR_DEF_CONTROL_TAB_PAGE_BORDER_DOWN_ON (C'0xDD,0xDD,0xDD') // Color of the enabled TabPage control frame when clicking on the control
#define CLR_DEF_CONTROL_TAB_PAGE_BORDER_OVER_ON (C'0xDD,0xDD,0xDD') // Color of the enabled TabPage control frame when hovering the mouse over the control

#define CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR (C'0xF0,0xF0,0xF0')   // TabPage control header background color
#define CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN (C'0xF0,0xF0,0xF0')   // Color of TabPage control header background when clicking on the control
#define CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER (C'0xF0,0xF0,0xF0')   // Color of TabPage control header background when hovering the mouse over the control
#define CLR_DEF_CONTROL_TAB_HEAD_OPACITY    (255)                 // TabPage header background opacity

#define CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON (C'0xFF,0xFF,0xFF')// Color of the enabled TabPage control header background
#define CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON (C'0xFF,0xFF,0xFF') // Color of the enabled TabPage control header background when clicking on the control
#define CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON (C'0xFF,0xFF,0xFF') // Color of the enabled TabPage control header background when clicking on the control

#define CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR (C'0xD9,0xD9,0xD9')   // TabPage control header frame color
#define CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN (C'0xD9,0xD9,0xD9') // Color of TabPage control header frame when clicking on the control
#define CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER (C'0xD9,0xD9,0xD9') // Color of TabPage control header frame when hovering the mouse over the control

#define CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR_ON (C'0xDD,0xDD,0xDD')// Color of the enabled TabPage control header frame
#define CLR_DEF_CONTROL_TAB_HEAD_BORDER_DOWN_ON (C'0xDD,0xDD,0xDD') // Color of the enabled TabPage control header frame when clicking on the control
#define CLR_DEF_CONTROL_TAB_HEAD_BORDER_OVER_ON (C'0xDD,0xDD,0xDD') // Color of the enabled TabPage control header frame when hovering the mouse over the control

#define DEF_CONTROL_LIST_MARGIN_X      (1)                        // Gap between columns in ListBox controls
#define DEF_CONTROL_LIST_MARGIN_Y      (0)                        // Gap between rows in ListBox controls

#define DEF_FONT                       ("Calibri")                // Default font
#define DEF_FONT_SIZE                  (8)                        // Default font size
#define DEF_CHECK_SIZE                 (12)                       // Verification flag default size
#define OUTER_AREA_SIZE                (16)                       // Size of one side of the outer area around the form workspace
#define DEF_FRAME_WIDTH_SIZE           (3)                        // Default form/panel/window frame width
//--- Graphical object parameters

Hier haben wir zwei Makro-Substitutionen, die den Abstand zwischen Zeilen in ListBox-Steuerelementen definieren. Bisher waren wir gezwungen, eine Höhe von 4 Pixel und eine Breite von 6 Pixel zu verwenden. Nachdem wir den Fehler behoben haben, dass sich das Aussehen der Elemente bei der Interaktion mit der Maus ändert, können wir nun die Mindestabstände zwischen den Zeilen in der Höhe und in der Breite festlegen.


Hinzufügen von drei neuen Typen zur Enumeration der grafischen Elementtypen:

//+------------------------------------------------------------------+
//| 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_ELEMENTS_LIST_BOX,           // Base list object of Windows Forms elements
   GRAPH_ELEMENT_TYPE_WF_LIST_BOX,                    // Windows Forms ListBox
   GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,            // Windows Forms CheckedListBox
   GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,             // Windows Forms ButtonListBox
   GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,                  // Windows Forms TabHeader
   GRAPH_ELEMENT_TYPE_WF_TAB_PAGE,                    // Windows Forms TabPage
   GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,                 // Windows Forms TabControl
  };
//+------------------------------------------------------------------+

Wir werden diese neuen Typen von grafischen Elementen benötigen, um den Typ des WinForms TabControl-Objekts selbst oder seiner Komponenten — der Registerkarte (TabHeader) oder der Registerkarte (TabPage) — zu bestimmen. Mit anderen Worten, das Objekt besteht aus einer Reihe von Registerkarten, und die Registerkarten bestehen aus einem Feld, in dem die angehängten Objekte platziert werden, sowie aus dem Titel des Feldes, durch dessen Anklicken die entsprechende Registerkarte aktiviert wird.

Der Registerkartentitel des grafischen Elements TabControl kann an vier Positionen platziert werden: rechts, links, oben und unten.
Erstellen wir eine neue Enumeration, um ihren Standort anzugeben:

//+------------------------------------------------------------------+
//| Control flag status                                              |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_CHEK_STATE
  {
   CANV_ELEMENT_CHEK_STATE_UNCHECKED,                 // Unchecked
   CANV_ELEMENT_CHEK_STATE_CHECKED,                   // Checked
   CANV_ELEMENT_CHEK_STATE_INDETERMINATE,             // Undefined
  };
//+------------------------------------------------------------------+
//| Location of an object inside a control                           |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_ALIGNMENT
  {
   CANV_ELEMENT_ALIGNMENT_TOP,                        // Top
   CANV_ELEMENT_ALIGNMENT_BOTTOM,                     // Bottom
   CANV_ELEMENT_ALIGNMENT_LEFT,                       // Left
   CANV_ELEMENT_ALIGNMENT_RIGHT,                      // Right
  };
//+------------------------------------------------------------------+
//| Integer properties of the graphical element on the canvas        |
//+------------------------------------------------------------------+


Bei der Erstellung der Namen der Konstanten wurde zuvor eine kleine Ungenauigkeit gemacht: Unsere Schaltfläche kann eine Umschalttaste sein, aber der Zustand der Schaltfläche „gedrückt/freigegeben“ ist kein Umschaltzustand. Es ist besser, den gedrückten Zustand in den Namen der Enumerationskonstante in StateOn umzubenennen.

Daher wurden alle Enumerationen, die die Farbe des gedrückten Zustands erwähnen, die vorher „COLOR_TOGGLE“ hieß, jetzt in „COLOR_STATE_ON“ umbenannt. Alle Korrekturen an den Namen der Enumerationskonstante wurden bereits in der Bibliothek vorgenommen und werden hier nur zur Information erwähnt.
Im Folgenden finden wir ein Beispiel für die Umbenennung einiger Konstanten in der besprochenen Datei in der Enumeration der Integer-Eigenschaften des grafischen Elements auf der Leinwand (canvas):

   CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_DOWN,           // Default control text color when clicking on the control
   CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER,           // Default control text color when hovering the mouse over the control
   CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON,             // Text color of the control which is on
   CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_DOWN,  // Default control text color when clicking on the control which is on
   CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_OVER,  // Default control text color when hovering the mouse over the control which is on
   CANV_ELEMENT_PROP_BACKGROUND_COLOR,                // Control background color
   CANV_ELEMENT_PROP_BACKGROUND_COLOR_OPACITY,        // Opacity of control background color
   CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN,     // Control background color when clicking on the control
   CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER,     // Control background color when hovering the mouse over the control
   CANV_ELEMENT_PROP_BACKGROUND_COLOR_STATE_ON,       // Background color of the control which is on
   CANV_ELEMENT_PROP_BACKGROUND_COLOR_STATE_ON_MOUSE_DOWN,// Control background color when clicking on the control which is on
   CANV_ELEMENT_PROP_BACKGROUND_COLOR_STATE_ON_MOUSE_OVER,// Control background color when hovering the mouse over control which is on
   CANV_ELEMENT_PROP_BOLD_TYPE,                       // Font width type
   CANV_ELEMENT_PROP_BORDER_STYLE,                    // Control frame style


Ganz am Ende derselben Enumeration fügen wir drei neue ganzzahlige Eigenschaften hinzu und erhöhen die Anzahl der ganzzahligen Objekteigenschaften von 85 auf 88:

   CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN,           // Horizontal display of columns in the ListBox control
   CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH,           // Width of each ListBox control column
   CANV_ELEMENT_PROP_TAB_MULTILINE,                   // Several lines of tabs in TabControl
   CANV_ELEMENT_PROP_TAB_ALIGNMENT,                   // Location of tabs inside the control
   CANV_ELEMENT_PROP_ALIGNMENT,                       // Location of an object inside the control
   
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (88)          // Total number of integer properties
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Number of integer properties not used in sorting
//+------------------------------------------------------------------+


Am Ende der Liste der möglichen Kriterien für die Sortierung von grafischen Elementen auf der Leinwand fügen wir drei neue Eigenschaften hinzu:

   SORT_BY_CANV_ELEMENT_LIST_BOX_MULTI_COLUMN,        // Sort by horizontal column display flag in the ListBox control
   SORT_BY_CANV_ELEMENT_LIST_BOX_COLUMN_WIDTH,        // Sort by the width of each ListBox control column
   SORT_BY_CANV_ELEMENT_TAB_MULTILINE,                // Sort by the flag of several rows of tabs in TabControl
   SORT_BY_CANV_ELEMENT_TAB_ALIGNMENT,                // Sort by the location of tabs inside the control
   SORT_BY_CANV_ELEMENT_ALIGNMENT,                    // Sort by the location of the object inside 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:

   MSG_LIB_TEXT_BUTTON_STATE_PRESSED,                 // Pressed
   MSG_LIB_TEXT_BUTTON_STATE_DEPRESSED,               // Released
   
   MSG_LIB_TEXT_TOP,                                  // Top
   MSG_LIB_TEXT_BOTTOM,                               // Bottom
   MSG_LIB_TEXT_LEFT,                                 // Left
   MSG_LIB_TEXT_RIGHT,                                // Right
   
   MSG_LIB_TEXT_CORNER_LEFT_UPPER,                    // Center of coordinates at the upper left corner of the chart
   MSG_LIB_TEXT_CORNER_LEFT_LOWER,                    // Center of coordinates at the lower left corner of the chart
   MSG_LIB_TEXT_CORNER_RIGHT_LOWER,                   // Center of coordinates at the lower right corner of the chart
   MSG_LIB_TEXT_CORNER_RIGHT_UPPER,                   // Center of coordinates at the upper right corner of the chart

...

   MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,        // CheckedListBox control
   MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,         // ButtonListBox control
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,              // Tab header
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_PAGE,                // TabPage control
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,             // TabControl
   MSG_GRAPH_OBJ_BELONG_PROGRAM,                      // Graphical object belongs to a program
   MSG_GRAPH_OBJ_BELONG_NO_PROGRAM,                   // Graphical object does not belong to a program

..

   MSG_CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN,       // Horizontal display of columns in the ListBox control
   MSG_CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH,       // Width of each ListBox control column
   MSG_CANV_ELEMENT_PROP_TAB_MULTILINE,               // Several lines of tabs in the control
   MSG_CANV_ELEMENT_PROP_TAB_ALIGNMENT,               // Location of tabs inside the control
   MSG_CANV_ELEMENT_PROP_ALIGNMENT,                   // Location of an object inside the control
//--- Real properties of graphical elements

//--- String properties of graphical elements
   MSG_CANV_ELEMENT_PROP_NAME_OBJ,                    // Graphical element object name
   MSG_CANV_ELEMENT_PROP_NAME_RES,                    // Graphical resource name
   MSG_CANV_ELEMENT_PROP_TEXT,                        // Graphical element text

  };
//+------------------------------------------------------------------+

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

   {"Нажата","Pressed"},
   {"Отжата","Depressed"},
   
   {"Сверху","Top"},
   {"Снизу","Bottom"},
   {"Слева","Left"},
   {"Справа","Right"},
   
   {"Центр координат в левом верхнем углу графика","Center of coordinates is in the upper left corner of the chart"},
   {"Центр координат в левом нижнем углу графика","Center of coordinates is in the lower left corner of the chart"},
   {"Центр координат в правом нижнем углу графика","Center of coordinates is in the lower right corner of the chart"},
   {"Центр координат в правом верхнем углу графика","Center of coordinates is in the upper right corner of the chart"},

...

   {"Элемент управления \"CheckedListBox\"","Control element \"CheckedListBox\""},
   {"Элемент управления \"ButtonListBox\"","Control element \"ButtonListBox\""},
   {"Заголовок вкладки","Tab header"},
   {"Элемент управления \"TabPage\"","Control element \"TabPage\""},
   {"Элемент управления \"TabControl\"","Control element \"TabControl\""},
   
   {"Графический объект принадлежит программе","The graphic object belongs to the program"},
   {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},

..

   {"Горизонтальное отображение столбцов в элементе управления ListBox","Display columns horizontally in a ListBox control"},
   {"Ширина каждого столбца элемента управления ListBox","The width of each column of the ListBox control"},
   {"Несколько рядов вкладок в элементе управления","Multiple rows of tabs in a control"},
   {"Местоположение вкладок внутри элемента управления","Location of tabs inside the control"},
   {"Местоположение объекта внутри элемента управления","Location of the object inside the control"},
   
//--- String properties of graphical elements
   {"Имя объекта-графического элемента","The name of the graphic element object"},
   {"Имя графического ресурса","Image resource name"},
   {"Текст графического элемента","Text of the graphic element"},

  };
//+---------------------------------------------------------------------+


Ich habe die neuen Arten von grafischen Elementen sowie ihre Beschreibungen hinzugefügt. Jetzt können wir also auf die Textnachrichtenindizes in der Methode zugreifen, die die Beschreibung des grafischen Elementtyps der grafischen Basisobjektklasse in \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh zurückgibt:

//+------------------------------------------------------------------+
//| Return the description of the graphical element type             |
//+------------------------------------------------------------------+
string CGBaseObj::TypeElementDescription(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   return
     (
      type==GRAPH_ELEMENT_TYPE_STANDARD               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD)              :
      type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)     :
      type==GRAPH_ELEMENT_TYPE_ELEMENT                ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT)               :
      type==GRAPH_ELEMENT_TYPE_SHADOW_OBJ             ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ)            :
      type==GRAPH_ELEMENT_TYPE_FORM                   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM)                  :
      type==GRAPH_ELEMENT_TYPE_WINDOW                 ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW)                :
      //--- WinForms
      type==GRAPH_ELEMENT_TYPE_WF_UNDERLAY            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY)           :
      type==GRAPH_ELEMENT_TYPE_WF_BASE                ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE)               :
      //--- Containers
      type==GRAPH_ELEMENT_TYPE_WF_CONTAINER           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER)          :
      type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX)           :
      type==GRAPH_ELEMENT_TYPE_WF_PANEL               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL)              :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER          ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER)         :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_PAGE            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_PAGE)           :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)        :
      //--- Standard controls
      type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE)        :
      type==GRAPH_ELEMENT_TYPE_WF_LABEL               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL)              :
      type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX)           :
      type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON)        :
      type==GRAPH_ELEMENT_TYPE_WF_BUTTON              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON)             :
      type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX)  :
      type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX)           :
      type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX    ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX)   :
      type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX     ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX)    :
      "Unknown"
     );
  }  
//+------------------------------------------------------------------+


Der Titel der Registerkarte von TabControl kann an den vier Seiten eines grafischen Objekts platziert werden. Später können wir auch andere Objekte haben, für die es notwendig sein wird, ihre Position innerhalb ihres Containers anzugeben. Daher sollten wir eine öffentliche Funktion erstellen, die die Beschreibung der Seite mit dem Standort des grafischen Elements in \MQL5\Include\DoEasy\Services\DELib.mqh zurückgibt:

//+------------------------------------------------------------------+
//| Return the description                                           |
//| of the object location inside the control                        |
//+------------------------------------------------------------------+
string AlignmentDescription(const ENUM_CANV_ELEMENT_ALIGNMENT alignment)
  {
   switch(alignment)
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :  return CMessage::Text(MSG_LIB_TEXT_TOP);     break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :  return CMessage::Text(MSG_LIB_TEXT_BOTTOM);  break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :  return CMessage::Text(MSG_LIB_TEXT_LEFT);    break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :  return CMessage::Text(MSG_LIB_TEXT_RIGHT);   break;
      default                             :  return "Unknown"; break;
     }
  }
//+------------------------------------------------------------------+
//| Return the flag of displaying the graphical                      |
//| object on a specified chart timeframe                            |
//+------------------------------------------------------------------+

Je nach dem übergebenen Typ des Objekts, das sich innerhalb des Containers befindet, wird die entsprechende Textnachricht zurückgegeben.


Sprechen wir nun noch ein wenig über Optimierung.

Wenn man mit dem Mauszeiger über ein Objekt fährt, kann dieses Objekt seine Farbe ändern. Derzeit wirkt die Methode zur Änderung der Farbe sofort. Dabei spielt es keine Rolle, dass die Farbe vielleicht schon genau die ist, die geändert werden soll. Dementsprechend belasten wir in diesem Fall das System übermäßig, indem wir die Farbe in genau dieselbe ändern. Um dies zu vermeiden, müssen wir die Farbe überprüfen, bevor wir sie ändern. Wenn die angegebene Farbe des Objekts genau mit der Farbe übereinstimmt, in die wir sie ändern wollen, brauchen wir nichts weiter zu tun, als die Methode zu verlassen.

In \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, und zwar in der Methode zur Farbeinstellung, wurden die angekündigten Verbesserungen vorgenommen:

//--- Set the main background color
   void              SetBackgroundColor(const color colour,const bool set_init_color)
                       {
                        if(this.BackgroundColor()==colour)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR,colour);
                        color arr[1];
                        arr[0]=colour;
                        this.SaveColorsBG(arr);
                        if(set_init_color)
                           this.SetBackgroundColorInit(this.BackgroundColor());
                       }
   void              SetBackgroundColors(color &colors[],const bool set_init_colors)
                       {
                        if(::ArrayCompare(colors,this.m_array_colors_bg)==0)
                           return;
                        this.SaveColorsBG(colors);
                        this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR,this.m_array_colors_bg[0]);
                        if(set_init_colors)
                           this.SetBackgroundColorsInit(colors);
                       }
//--- Set the background color when clicking on the control
   void              SetBackgroundColorMouseDown(const color colour)
                       {
                        if(this.BackgroundColorMouseDown()==colour)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN,colour);
                        color arr[1];
                        arr[0]=colour;
                        this.SaveColorsBGMouseDown(arr);
                       }
   void              SetBackgroundColorsMouseDown(color &colors[])
                       {
                        if(::ArrayCompare(colors,this.m_array_colors_bg_dwn)==0)
                           return;
                        this.SaveColorsBGMouseDown(colors);
                        this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN,this.m_array_colors_bg_dwn[0]);
                       }
//--- Set the background color when hovering the mouse over control
   void              SetBackgroundColorMouseOver(const color colour)
                       {
                        if(this.BackgroundColorMouseOver()==colour)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER,colour);
                        color arr[1];
                        arr[0]=colour;
                        this.SaveColorsBGMouseOver(arr);
                       }
   void              SetBackgroundColorsMouseOver(color &colors[])
                       {
                        if(::ArrayCompare(colors,this.m_array_colors_bg_ovr)==0)
                           return;
                        this.SaveColorsBGMouseOver(colors);
                        this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER,this.m_array_colors_bg_ovr[0]);
                       }
//--- Set the initial main background color

Bei den Methoden, bei denen eine Farbe verwendet wird, vergleichen wir die Farbe des Objekts mit der an die Methode übergebenen Farbe. In den Methoden, die das Farbfeld anwenden, müssen wir zwei Felder auf Gleichheit vergleichen. Dies geschieht mit Hilfe der Funktion ArrayCompare(), die Null zurückgibt, wenn die verglichenen Arrays gleich sind.

Fügen wir ganz am Ende des geschützten Konstruktors den Namen der Klasse und den Typ des erstellten Objekts in eine Zeichenkette ein, die den Fehler bei der Erstellung des Objekts anzeigt:

   else
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.m_name);
     }
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+

Zuvor wurde während des Debuggens sowie wenn das Objekt nicht erstellt wurde, der entsprechende Eintrag mit seinem Namen an das Protokoll gesendet. Dies war nicht ausreichend, da nicht klar war, von welcher Klasse die Nachricht angezeigt wurde und um welche Art von Objekt es sich handelte. Jetzt wird es einfacher sein, die richtige Klasse zu finden, von der die Fehlermeldung stammt.

Gleichzeitig wird aber der Objekttyp nicht immer korrekt angegeben, da fast alle grafischen Elemente eine komplexe Vererbungshierarchie haben und sich ihr Typ ändert, wenn alle Konstruktoren in der Hierarchie erfolgreich abgearbeitet werden, angefangen beim einfachsten bis hin zum letzten, in dem der korrekte Typ des erstellten Objekts angegeben werden muss. Aber es gibt auch eine positive Seite. Wenn wir wissen, welche Art von Objekt wir erstellen, und wenn wir sehen, in welchem Stadium seine Erstellung fälschlicherweise abgebrochen wurde, können wir bereits wissen, wo (in welcher Klasse) wir nach einem Fehler suchen müssen, da der Typ, in dessen Klasse der Konstruktor seine Arbeit fälschlicherweise beendet hat, jetzt angezeigt wird.


Wir haben die Methoden für die Arbeit mit der Farbe eines grafischen Elements in verschiedenen Klassen von verschiedenen Steuerelementen, die gerechtfertigt ist — wenn das Objekt nicht über Eigenschaften, deren Farbe geändert werden muss, dann gibt es keine Notwendigkeit, Methoden in sie zu schaffen, um mit der fehlenden Eigenschaft zu arbeiten. Im Gegensatz zum Objekt verfügt sein Nachkomme bereits über diese Eigenschaft und kann sie verarbeiten. Daher implementieren wir die Methoden zur Behandlung der unterstützten Eigenschaft.

In der Formularobjektklassendatei \MQL5\Include\DoEasy\Objects\Graph\Form.mqh fügen wir die zuvor beschriebenen Änderungen den Farbbehandlungsmethoden hinzu:

//+------------------------------------------------------------------+
//| Methods of simplified access to object properties                |
//+------------------------------------------------------------------+
//--- (1) Set and (2) return the control frame color
   void              SetBorderColor(const color colour,const bool set_init_color)
                       {
                        if(this.BorderColor()==colour)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR,colour);
                        if(set_init_color)
                           this.SetBorderColorInit(colour);
                       }
   color             BorderColor(void)                            const { return (color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR);             }
//--- (1) Set and (2) return the control frame color when clicking the control
   void              SetBorderColorMouseDown(const color colour)
                       {
                        if(this.BorderColorMouseDown()==colour)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN,colour);
                       }
   color             BorderColorMouseDown(void)                   const { return (color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN);  }
//--- (1) Set and (2) return the control frame color when hovering the mouse over the control
   void              SetBorderColorMouseOver(const color colour)
                       {
                        if(this.BorderColorMouseOver()==colour)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_OVER,colour);
                       }
   color             BorderColorMouseOver(void)                   const { return (color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_OVER);  }


In der Methode, die den Formularrahmen zeichnet, wurde bisher standardmäßig ein einfacher Rahmen gezeichnet. Wenn jedoch der Methode der Rahmenstil FRAME_STYLE_NONE (es gibt keinen Rahmen) übergeben wird, zeichnet die Methode trotzdem einen einfachen Rahmen. Bei vielen Objekten wird der Typ des Rahmens vor dem Aufruf dieser Methode überprüft, und die Methode wird nur aufgerufen, wenn das Objekt einen Rahmen hat. Um jedoch mögliche künftige Auslassungen zu berücksichtigen, sollten wir das Fehlen des Standardrahmens implementieren, während der einfache Rahmen im Fall des Operators „switch“ gezeichnet wird:

//+------------------------------------------------------------------+
//| Draw the form frame                                              |
//+------------------------------------------------------------------+
void CForm::DrawFormFrame(const int wd_top,              // Frame upper segment width
                          const int wd_bottom,           // Frame lower segment width
                          const int wd_left,             // Frame left segment width
                          const int wd_right,            // Frame right segment width
                          const color colour,            // Frame color
                          const uchar opacity,           // Frame opacity
                          const ENUM_FRAME_STYLE style)  // Frame style
  {
//--- Depending on the passed frame style
   switch(style)
     {
      //--- draw a dimensional (convex) frame
      case FRAME_STYLE_BEVEL  : this.DrawFrameBevel(0,0,this.Width(),this.Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity);   break;
      //--- draw a dimensional (concave) frame
      case FRAME_STYLE_STAMP  : this.DrawFrameStamp(0,0,this.Width(),this.Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity);   break;
      //--- draw a flat frame
      case FRAME_STYLE_FLAT   : this.DrawFrameFlat(0,0,this.Width(),this.Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity);    break;
      //--- draw a simple frame
      case FRAME_STYLE_SIMPLE : this.DrawFrameSimple(0,0,this.Width(),this.Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity);  break;
      //---FRAME_STYLE_NONE
      default                 : break;
     }
  }
//+------------------------------------------------------------------+


In der Behandlung für das letzte Mausereignis die Bedingung hinzu, dass das letzte Mausereignis der undefinierte Zustand war, während der aktuelle Status darin besteht, dass die Schaltfläche außerhalb des Formulars nicht gedrückt wurde und der Zustand undefiniert ist:

//+------------------------------------------------------------------+
//| Last mouse event handler                                         |
//+------------------------------------------------------------------+
void CForm::OnMouseEventPostProcessing(void)
  {
   ENUM_MOUSE_FORM_STATE state=this.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              :
      case MOUSE_FORM_STATE_NONE                            :
        if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED  || 
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED         || 
           this.MouseEventLast()==MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED        ||
           this.MouseEventLast()==MOUSE_EVENT_NO_EVENT)
          {
           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(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;
     }
  }
//+------------------------------------------------------------------+

Solche Situationen müssen ebenfalls behandelt werden, um die Farben des Objekts wieder in einen normalen Zustand zu versetzen. Aber das Wichtigste an dieser Methode zur Optimierung der Arbeit mit der Maus ist die Tatsache, dass das Objekt jedes Mal neu gezeichnet wurde. Mit anderen Worten, wenn die Farbe geändert wurde, wurde das Objekt komplett mit neuen Farben gezeichnet. Dementsprechend wurden auch alle daran hängenden Objekte neu gezeichnet, was zu einem visuell wahrnehmbaren „Blinken“ der grafischen Oberfläche führte. Jetzt wurde das Neuzeichnen entfernt und die Objekte ändern einfach ihre Farbe, ohne dass sie komplett neu gezeichnet werden müssen. Gleichzeitig bleibt die Farbe, die bereits mit der Farbe übereinstimmt, in die sie geändert werden soll, unverändert. Wir verlassen einfach die Farbwechselmethode. Wir haben diese Änderungen bereits oben festgelegt.

Jetzt funktioniert die Interaktion von grafischen Elementen mit dem Cursor korrekt. Ich schließe weitere Verbesserungen zur Optimierung und Behebung der festgestellten Mängel in der Zukunft nicht aus.


Wie bereits erwähnt, befinden sich einige Methoden zur Behandlung der Farbe von Objekten in den Klassen, deren Objekte diese Eigenschaften unterstützen. Die Methoden für die Arbeit mit der Textfarbe bei der Interaktion mit dem Cursor befinden sich in der Basisobjektklasse von WinForms-Objekten. Hier werden wir die Methoden für die Arbeit mit dem Text von Objekten für die Optimierung abschließen.

In \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh, der WinForms-Basisklasse, und zwar in ihrem geschützten Abschnitt, deklarieren wir die Variable zum Speichern der anfänglichen Textfarbe im Objektstatus „aktiviert“:

//+------------------------------------------------------------------+
//| Form object class                                                |
//+------------------------------------------------------------------+
class CWinFormBase : public CForm
  {
protected:
   color             m_fore_color_init;                        // Initial color of the control text
   color             m_fore_state_on_color_init;               // Initial color of the control text when the control is "ON"
private:


Verbessern wir die Methoden zur Optimierung des Farbhandlings genauso wie oben:

//--- (1) Set and (2) return the default text color of all panel objects
   void              SetForeColor(const color clr,const bool set_init_color)
                       {
                        if(this.ForeColor()==clr)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR,clr);
                        if(set_init_color)
                           this.SetForeColorInit(clr);
                       }
   color             ForeColor(void)                           const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR);                     }
//--- (1) Set and (2) return the initial default text color of all panel objects
   void              SetForeColorInit(const color clr)               { this.m_fore_color_init=clr;                                                       }
   color             ForeColorInit(void)                       const { return (color)this.m_fore_color_init;                                             }
//--- (1) Set and (2) return the default text color opacity of all panel objects
   void              SetForeColorOpacity(const uchar value)
                       {
                        if(this.ForeColorOpacity()==value)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY,value);
                       }
   uchar             ForeColorOpacity(void)                    const { return (uchar)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY);             }
//--- (1) Set and (2) return the control text color when clicking the control
   void              SetForeColorMouseDown(const color clr)
                       {
                        if(this.ForeColorMouseDown()==clr)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_DOWN,clr);
                       }
   color             ForeColorMouseDown(void)                  const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_DOWN);          }
//--- (1) Set and (2) return the control text color when hovering the mouse over the control
   void              SetForeColorMouseOver(const color clr)
                       {
                        if(this.ForeColorMouseOver()==clr)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER,clr);
                       }
   color             ForeColorMouseOver(void)                  const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER);          }


Wir fügen die Methoden zum Ändern der Textfarbe bei der Interaktion mit dem Mauszeiger hinzu, während das Objekt aktiviert ist:

   color             ForeColorMouseOver(void)                  const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER);          }
   
//--- (1) Set and (2) return the initial enabled default text color of all panel objects
   void              SetForeStateOnColorInit(const color clr)        { this.m_fore_state_on_color_init=clr;                                              }
   color             ForeStateOnColorInit(void)                const { return (color)this.m_fore_state_on_color_init;                                    }
//--- (1) Set and (2) return the main text color for the "enabled" status
   void              SetForeStateOnColor(const color colour,const bool set_init_color)
                       {
                        if(this.ForeStateOnColor()==colour)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON,colour);
                        if(set_init_color)
                           this.SetForeStateOnColorInit(colour);
                       }
   color             ForeStateOnColor(void)                    const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON);            }
   
//--- (1) Set and (2) return the text color when clicking on the control for the "enabled" status
   void              SetForeStateOnColorMouseDown(const color colour)
                       {
                        if(this.ForeStateOnColorMouseDown()==colour)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_DOWN,colour);
                       }
   color             ForeStateOnColorMouseDown(void)           const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_DOWN); }
                       
//--- (1) Set and (2) return the text color when hovering the mouse over the control for the "enabled" status
   void              SetForeStateOnColorMouseOver(const color colour)
                       {
                        if(this.ForeStateOnColorMouseOver()==colour)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_OVER,colour);
                       }
   color             ForeStateOnColorMouseOver(void)           const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_OVER); }
   
//--- (1) Set and (2) return the element text

Jetzt können wir die Farbe des Textes bei der Interaktion mit der Maus und in verschiedenen Zuständen des Objekts ändern, so wie sich auch seine anderen Farben ändern — Hintergrund und Rahmen.

Im Klassenkonstruktor fügen wir die Einstellung der Textfarbe des aktivierten Objekts hinzu:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CWinFormBase::CWinFormBase(const long chart_id,
                           const int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CForm(chart_id,subwindow,name,x,y,w,h)
  {
//--- Set the graphical element and library object types as a base WinForms object
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE);
   this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
//--- Initialize all variables
   this.SetText("");
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetForeStateOnColor(this.ForeColor(),true);
   this.SetForeStateOnColorMouseDown(this.ForeColor());
   this.SetForeStateOnColorMouseOver(this.ForeColor());
   this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY);
   this.SetFontBoldType(FW_TYPE_NORMAL);
   this.SetMarginAll(0);
   this.SetPaddingAll(0);
   this.SetBorderSizeAll(0);
   this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetAutoSize(false,false);
   CForm::SetCoordXInit(x);
   CForm::SetCoordYInit(y);
   CForm::SetWidthInit(w);
   CForm::SetHeightInit(h);
   this.m_shadow=false;
   this.m_gradient_v=true;
   this.m_gradient_c=false;
  }
//+------------------------------------------------------------------+

Standardmäßig ändern sich die Farben des Textes im „aktivierten“ Zustand nicht, wenn man mit dem Mauszeiger über das Objekt fährt und darauf klickt — sie sind gleich der Farbe des Textes im normalen Zustand des Objekts. Bei Objekten abgeleiteter Klassen können diese Farben geändert werden, um die Interaktion des Objekts mit der Maus visuell darzustellen.


Ganz am Ende der Methode, die die Beschreibung der Integer-Eigenschaft des Elements zurückgibt, fügen wir die Codeblöcke für die Rückgabe der Beschreibung neuer grafischer Elementeigenschaften hinzu:

      property==CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER  ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::ColorToString((color)this.GetProperty(property),true)
         )  :
      property==CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN  ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH  ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TAB_MULTILINE                ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_MULTILINE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TAB_ALIGNMENT                ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_ALIGNMENT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(property))
         )  :
      property==CANV_ELEMENT_PROP_ALIGNMENT                    ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_ALIGNMENT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(property))
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

Ich hätte die ersten beiden Blöcke im vorherigen Artikel hinzufügen sollen. Aber besser spät als nie...


In dem WinForms-Objekt CheckBox haben wir bisher ein Kontrollkästchen entsprechend den fest kodierten Koordinaten in seinem Feld gezeichnet. Dies ist nicht korrekt, denn wenn die Größe des Feldes geändert wird, wird das Kontrollkästchen entlang der gleichen hart kodierten Koordinaten gedehnt oder gestaucht. Anstatt also die Koordinaten mit einem Abstand in Pixeln von den Rändern des Kontrollkästchens zu verwenden, sollten wir relative Koordinaten als Prozentsatz der Feldgröße verwenden. Beheben wir das:

In \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh, und zwar in der Methode, die das Kontrollkästchen für den angegebenen Zustand anzeigt, berechnen wir die relativen Koordinaten des Kontrollkästchens in den Zuständen „aktiviert“ und „undefiniert“. Ich werde Berechnungen in realen Werten durchführen, sie dann auf Ganzzahlen reduzieren und die Koordinaten an die primitiven Zeichenmethoden der Klasse CCanvas übergeben:

//+------------------------------------------------------------------+
//| Display the checkbox for the specified state                     |
//+------------------------------------------------------------------+
void CCheckBox::ShowControlFlag(const ENUM_CANV_ELEMENT_CHEK_STATE state)
  {
//--- Draw a filled rectangle of the selection checkbox area
   this.DrawRectangleFill(this.m_check_x,this.m_check_y,this.m_check_x+this.CheckWidth(),this.m_check_y+this.CheckHeight(),this.CheckBackgroundColor(),this.CheckBackgroundColorOpacity());
//--- Draw the rectangle of checkbox boundaries
   this.DrawRectangle(this.m_check_x,this.m_check_y,this.m_check_x+this.CheckWidth(),this.m_check_y+this.CheckHeight(),this.CheckBorderColor(),this.CheckBorderColorOpacity());
//--- Create X and Y coordinate arrays for drawing a polyline
   double x=(double)this.m_check_x;
   double y=(double)this.m_check_y;
   double w=(double)this.m_check_w;
   double h=(double)this.m_check_h;
//--- Calculate coordinates as double values and write them to arrays as integers
   int array_x[]={int(x+w*0.2), int(x+w*0.45), int(x+w*0.85), int(x+w*0.45)};
   int array_y[]={int(y+h*0.5), int(y+h*0.6),  int(y+h*0.3),  int(y+h*0.75)};
//--- Depending on the checkbox status passed to the method
   switch(state)
     {
      //--- Checked box
      case CANV_ELEMENT_CHEK_STATE_CHECKED :
        //--- First, draw a filled polygon inside the checkbox borders,
        //--- as well as a smoothed polygon in the form of a checkmark on top of it
        this.DrawPolygonFill(array_x,array_y,this.CheckFlagColor(),this.CheckFlagColorOpacity());
        this.DrawPolygonAA(array_x,array_y,this.CheckFlagColor(),this.CheckFlagColorOpacity());
        break;
      //--- Undefined state
      case CANV_ELEMENT_CHEK_STATE_INDETERMINATE :
        //--- Draw a filled rectangle inside the checkbox boundaries
        this.DrawRectangleFill(int(x+w*0.3),int(y+h*0.3),int(x+w*0.7),int(y+h*0.7),this.CheckFlagColor(),this.CheckFlagColorOpacity());
        break;
      //--- Unchecked checkbox
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Jetzt wird das Häkchen korrekt angezeigt, wenn die Größe des Kontrollkästchens geändert wird.

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

In der Deklaration der Methode, die die Farbe für den Status „enabled“ (aktiviert) festlegt, fügen wir drei formale Farbparameter für den Status „enabled“ hinzu:

//--- Set the colors for the 'enabled' status
   void              SetStateOnColors(const color back,
                                      const color back_down,
                                      const color back_over,
                                      const color fore,
                                      const color fore_down,
                                      const color fore_over,
                                      const bool set_init_color);

Außerhalb des Klassenkörpers, d. h. im Code der Methodenimplementierung, fügen wir die Einstellung der übergebenen Farben zu den Objekteigenschaften hinzu:

//+------------------------------------------------------------------+
//| Set the colors for the toggle element 'enabled' status           |
//+------------------------------------------------------------------+
void CButton::SetStateOnColors(const color back,
                               const color back_down,
                               const color back_over,
                               const color fore,
                               const color fore_down,
                               const color fore_over,
                               const bool set_init_color)
  {
   this.SetBackgroundStateOnColor(back,set_init_color);
   this.SetBackgroundStateOnColorMouseDown(back_down);
   this.SetBackgroundStateOnColorMouseOver(back_over);
   this.SetForeStateOnColor(fore,set_init_color);
   this.SetForeStateOnColorMouseDown(fore_down);
   this.SetForeStateOnColorMouseOver(fore_over);
  }
//+------------------------------------------------------------------+


In der Methode, die das Toggle-Flag des Steuerelements setzt, fügen wir die Übergabe der Textfarbe für verschiedene Zustände der Interaktion des aktivierten Objekts mit dem Mauszeiger hinzu:

//--- (1) Set and (2) return the control Toggle flag
   void              SetToggleFlag(const bool flag)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE,flag);
                        if(this.Toggle())
                           this.SetStateOnColors
                             (
                              this.BackgroundStateOnColor(),this.BackgroundStateOnColorMouseDown(),this.BackgroundStateOnColorMouseOver(),
                              this.ForeStateOnColor(),this.ForeStateOnColorMouseDown(),this.ForeStateOnColorMouseOver(),true
                             );
                       }

In der Methode, die den Status des Toggle-Steuerelements festlegt, fügen wir den Objekteigenschaften je nach Zustand der Schaltfläche die Farben hinzu:

//--- (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.SetBackgroundColor(this.BackgroundStateOnColor(),false);
                           this.SetForeColor(this.ForeStateOnColor(),false);
                           this.UnpressOtherAll();
                          }
                        else
                          {
                           this.SetBackgroundColor(this.BackgroundColorInit(),false);
                           this.SetForeColor(this.ForeColorInit(),false);
                           this.SetBorderColor(this.BorderColorInit(),false);
                          }
                       }

Alle Methoden, die die Zeichenkette „ColorToggleON“ in ihrem Namen haben, wurden umbenannt. Die Zeichenkette wurde durch „StateOnColor“ ersetzt, und alle Enumerationskonstanten wurden entsprechend umbenannt. Als Beispiel ist hier die Methode zur Einstellung der Haupthintergrundfarbe für den Status „aktiviert“ zu sehen:

   void              SetBackgroundStateOnColor(const color colour,const bool set_init_color)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_STATE_ON,colour);
                        color arr[1];
                        arr[0]=colour;
                        this.CopyArraysColors(this.m_array_colors_bg_tgl,arr,DFUN);
                        if(set_init_color)
                           this.CopyArraysColors(this.m_array_colors_bg_tgl_init,arr,DFUN);
                       }

Im Klassenkonstruktor fügen wir Farben für die aktivierte Schaltfläche für drei Zustände der Interaktion mit der Maus hinzu:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CButton::CButton(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_BUTTON);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BUTTON);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetCoordX(x);
   this.SetCoordY(y);
   this.SetWidth(w);
   this.SetHeight(h);
   this.Initialize();
   this.SetBackgroundColor(CLR_DEF_CONTROL_STD_BACK_COLOR,true);
   this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_STD_MOUSE_DOWN);
   this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_STD_MOUSE_OVER);
   this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_STD_BACK_COLOR_ON,true);
   this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_STD_BACK_DOWN_ON);
   this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_STD_BACK_OVER_ON);
   this.SetOpacity(CLR_DEF_CONTROL_STD_OPACITY);
   this.SetTextAlign(ANCHOR_CENTER);
   this.SetMarginAll(3);
   this.SetWidthInit(this.Width());
   this.SetHeightInit(this.Height());
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetToggleFlag(false);
   this.SetState(false);
   this.Redraw(false);
  }
//+------------------------------------------------------------------+

In der Ereignisbehandlung des Mauszeigers in Bezug auf die Schaltfläche fügen wir die Einstellung der Textfarbe entsprechend dem Interaktionsstatus hinzu:

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CButton::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- If this is a simple button, set the background color for the "The cursor is over the active area, the mouse button is not clicked" status
   if(!this.Toggle())
     {
      this.SetBackgroundColor(this.BackgroundColorMouseOver(),false);
      this.SetForeColor(this.ForeColorMouseOver(),false);
     }
//--- If this is the toggle button, set the background color for the status depending on whether the button is pressed or not
   else
     {
      this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseOver() : this.BackgroundColorMouseOver(),false);
      this.SetForeColor(this.State() ? this.ForeStateOnColorMouseOver() : this.ForeColorMouseOver(),false);
     }
//--- Set the frame color for the status
   this.SetBorderColor(this.BorderColorMouseOver(),false);
//--- Redraw the object
   this.Redraw(false);
  }
//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| a mouse button is clicked (any)                                  |
//+------------------------------------------------------------------+
void CButton::MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- If this is a simple button, set the background color for the "The cursor is over the active area, the mouse button is clicked" status
   if(!this.Toggle())
     {
      this.SetBackgroundColor(this.BackgroundColorMouseDown(),false);
      this.SetForeColor(this.ForeColorMouseDown(),false);
     }
//--- If this is the toggle button, set the background color for the status depending on whether the button is pressed or not
   else
     {
      this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseDown() : this.BackgroundColorMouseDown(),false);
      this.SetForeColor(this.State() ? this.ForeStateOnColorMouseDown() : this.ForeColorMouseDown(),false);
     }
//--- Set the frame color for the status
   this.SetBorderColor(this.BorderColorMouseDown(),false);
//--- Redraw the object
   this.Redraw(false);
  }
//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| left mouse button released                                       |
//+------------------------------------------------------------------+
void CButton::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())
     {
      //--- If this is a simple button, set the initial background and text color
      if(!this.Toggle())
        {
         this.SetBackgroundColor(this.BackgroundColorInit(),false);
         this.SetForeColor(this.ForeColorInit(),false);
        }
      //--- If this is the toggle button, set the initial background and text color depending on whether the button is pressed or not
      else
        {
         this.SetBackgroundColor(!this.State() ? this.BackgroundColorInit() : this.BackgroundStateOnColorInit(),false);
         this.SetForeColor(!this.State() ? this.ForeColorInit() : this.ForeStateOnColorInit(),false);
        }
      //--- Set the initial frame color
      this.SetBorderColor(this.BorderColorInit(),false);
      //--- Send the test message to the journal
      Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel"));
     }
//--- The mouse button released within the element means a  click on the control
   else
     {
      //--- If this is a simple button, set the background and text color for "The cursor is over the active area" status
      if(!this.Toggle())
        {
         this.SetBackgroundColor(this.BackgroundColorMouseOver(),false);
         this.SetForeColor(this.ForeColorMouseOver(),false);
        }
      //--- If this is the toggle button,
      else
        {
         //--- if the button does not work in the group, set its state to the opposite,
         if(!this.GroupButtonFlag())
            this.SetState(!this.State());
         //--- if the button is not pressed yet, set it to the pressed state
         else if(!this.State())
            this.SetState(true);
         //--- set the background and text color for "The cursor is over the active area" status depending on whether the button is clicked or not
         this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseOver() : this.BackgroundColorMouseOver(),false);
         this.SetForeColor(this.State() ? this.ForeStateOnColorMouseOver() : this.ForeColorMouseOver(),false);
        }
      //--- Send the test message to the journal
      Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.State()=",this.State(),", ID=",this.ID(),", Group=",this.Group());
      //--- Set the frame color for "The cursor is over the active area" status
      this.SetBorderColor(this.BorderColorMouseOver(),false);
     }
//--- Redraw the object
   this.Redraw(false);
  }
//+------------------------------------------------------------------+

In der letzten Mausereignisbehandlung fügen wir die Wiederherstellung einer Objekttextfarbe in Abhängigkeit von seinem Status (aktiviert/deaktiviert) hinzu:

//+------------------------------------------------------------------+
//| 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.BackgroundStateOnColor() : this.BackgroundColorInit(),false);
           this.SetForeColor(this.State() ? this.ForeStateOnColor() : this.ForeColorInit(),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;
     }
  }
//+------------------------------------------------------------------+

In der Methode, die den Schaltflächenstatus für alle Schaltflächen der gleichen Gruppe im Container auf „freigegeben“ setzt, fügen wir die Einstellung der Farbe des Textes und des Rahmens auf ihre ursprünglichen Werte hinzu:

//+------------------------------------------------------------------+
//| 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);
         obj.SetForeColor(obj.ForeColorInit(),false);
         obj.SetBorderColor(obj.BorderColorInit(),false);
         //--- Redraw the object to display the changes
         obj.Redraw(false);
        }
     }
  }
//+------------------------------------------------------------------+

Jetzt können Schaltflächen die Farbe des auf ihnen angezeigten Textes je nach Zustand der Schaltfläche (gedrückt/losgelassen) bei der Interaktion mit dem Mauszeiger ändern.

In der Objektklasse ElementsListBox in \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ElementsListBox.mqh können wir jetzt in der Methode, die die Koordinaten des nächsten in der Liste platzierten Objekts zurückgibt, die Standardwerte verwenden, die in den zuvor erstellten Makrosubstitutionen festgelegt wurden, anstatt die Abstände der Objekte zueinander in Pixeln hart zu codieren. Die Standardwerte sind jetzt minimal. Derzeit ändern sich die Farben unabhängig von den Abständen der Objekte zueinander korrekt:

//+------------------------------------------------------------------+
//| Return the coordinates of the next object placed in the list     |
//+------------------------------------------------------------------+
void CElementsListBox::GetCoordsObj(CWinFormBase *obj,int &x,int &y)
  {
//--- Save the coordinates passed to the method in the variables
   int coord_x=x;
   int coord_y=y;
//--- If the flag of using multiple columns is not set,
   if(!this.MultiColumn())
     {
      //--- set the X coordinate the same as the one passed to the method,
      //--- set the Y coordinate for the first object in the list to be equal to the one passed to the method,
      //--- set the rest 4 pixels lower than the bottom edge of the previous object located above.
      //--- After setting the coordinates to the variables, leave the method
      x=coord_x;
      y=(obj==NULL ? coord_y : obj.BottomEdgeRelative()+DEF_CONTROL_LIST_MARGIN_Y);
      return;
     }
//--- If multiple columns can be used
//--- If this is the first object in the list, 
   if(obj==NULL)
     {
      //--- set the coordinates the same as those passed to the method and leave
      x=coord_x;
      y=coord_y;
      return;
     }
//--- If this is not the first object in the list
//--- If (the bottom border of the previous object + 4 pixels) is below the bottom border of the ListBox panel (the next object will go beyond the borders),
   if(obj.BottomEdge()+DEF_CONTROL_LIST_MARGIN_Y>this.BottomEdge())
     {
      //--- If the columns width is zero, then the X coordinate of the created object will be the right border of the previous object + 6 pixels
      //--- Otherwise, if the width of the columns is greater than zero, then the X coordinate of the created object will be the X coordinate of the previous one + the column width
      //--- The Y coordinate will be the value passed to the method (start placing objects in a new column)
      x=(this.ColumnWidth()==0 ? obj.RightEdgeRelative()+DEF_CONTROL_LIST_MARGIN_X : int(obj.CoordXRelative()+this.ColumnWidth()));
      y=coord_y;
     }
//--- If the created object is placed within the ListBox panel,
   else
     {
      //--- the X coordinate of the created object will be the offset of the previous one from the panel edge minus the width of its frame,
      //--- the Y coordinate will be the lower border of the previous object located above plus 4 pixels
      x=obj.CoordXRelative()-this.BorderSizeLeft();
      y=obj.BottomEdgeRelative()+DEF_CONTROL_LIST_MARGIN_Y+(this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX ? 2 : 0);
     }
  }
//+------------------------------------------------------------------+

Fügen wir in der WinForms-Objektklasse ListBox in \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ListBox.mqh die neuen formalen Parameter für die Angabe der Spaltenbreite und das Flags für die automatische Größenanpassung des Containers an seinen Inhalt zur Deklaration der Methode hinzu, die die Liste mit der Anzahl der Zeichenfolgen erstellt:

public:
//--- Create a list from the specified number of rows (Label objects)
   void              CreateList(const int line_count,const int new_column_width=0,const bool autosize=true);
//--- Constructor

Im Code der Methodenimplementierung berechnen wir die Breite der erstellten Zeichenketten in Abhängigkeit von der an die Methode übergebenen Spaltenbreite und fügen die Einstellung der Farben für den Status „aktiviert“ für das erstellte Objekt hinzu:

//+-------------------------------------------------------------------+
//| Create the list from the specified number of rows (Button objects)|
//+-------------------------------------------------------------------+
void CListBox::CreateList(const int count,const int new_column_width=0,const bool autosize=true)
  {
//--- Create the pointer to the Button object
   CButton *obj=NULL;
//--- Calculate the width of the created object depending on the specified column width
   int width=(new_column_width>0 ? new_column_width : this.Width()-this.BorderSizeLeft()-this.BorderSizeRight());
//--- Create the specified number of Button objects
   CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_BUTTON,count,0,0,width,15,new_column_width,autosize);
//--- In the loop by the created number of objects
   for(int i=0;i<this.ElementsTotal();i++)
     {
      //--- Get the created object from the list by the loop index
      obj=this.GetElement(i);
      //--- If the object could not be obtained, send the appropriate message to the log and move on to the next one
      if(obj==NULL)
        {
         ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_BUTTON));
         continue;
        }
      //--- Set left center text alignment
      obj.SetTextAlign(ANCHOR_LEFT);
      //--- Set the object text
      obj.SetFontSize(8);
      obj.SetText(" ListBoxItem"+string(i+1));
      //--- Set the background, text and frame color
      obj.SetBackgroundStateOnColor(clrDodgerBlue,true);
      obj.SetBackgroundStateOnColorMouseOver(obj.ChangeColorLightness(obj.BackgroundStateOnColor(),-5));
      obj.SetBackgroundStateOnColorMouseDown(obj.ChangeColorLightness(obj.BackgroundStateOnColor(),-10));
      obj.SetForeStateOnColor(this.BackgroundColor(),true);
      obj.SetForeStateOnColorMouseOver(obj.ChangeColorLightness(obj.ForeStateOnColor(),-5));
      obj.SetForeStateOnColorMouseDown(obj.ChangeColorLightness(obj.ForeStateOnColor(),-10));
      obj.SetBorderColor(obj.BackgroundColor(),true);
      obj.SetBorderColorMouseDown(obj.BackgroundColorMouseDown());
      obj.SetBorderColorMouseOver(obj.BackgroundColorMouseOver());
      //--- Set the flags of the toggle and group buttons
      obj.SetToggleFlag(true);
      obj.SetGroupButtonFlag(true);
     }
//--- If the flag of auto resizing the base object is passed to the method,
//--- set the auto resize mode to "increase and decrease"
   if(autosize)
      this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,false);
  }
//+------------------------------------------------------------------+

Der Container ändert seine Größe nur noch, wenn das Flag in den Methodeneingaben gesetzt ist.

Machen wir ähnliche Verbesserungen an der Objektklasse CheckedListBox in \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckedListBox.mqh.

In der Deklaration der Methode, die die angegebene Anzahl von CheckBox-Objekten erstellt, fügen wir einen neuen formalen Parameter hinzu — das Flag des Containers auto resize, um seinen Inhalt anzupassen:

public:
//--- Create the specified number of CheckBox objects
   void              CreateCheckBox(const int count,const int width,const int new_column_width=0,const bool autosize=true);
//--- Constructor

Im Code der Methodenimplementierung übergeben wir das Flag an die Methode zur Erstellung der angegebenen Anzahl von Elementen. Zum Schluss fügen wir die Überprüfung des gleichen Flags für den Start des Containers automatische Größenanpassung an die Anzahl der neuen Objekte in ihm erstellt passen:

//+------------------------------------------------------------------+
//| Create the specified number of CheckBox objects                  |
//+------------------------------------------------------------------+
void CCheckedListBox::CreateCheckBox(const int count,const int width,const int new_column_width=0,const bool autosize=true)
  {
//--- Create a pointer to the CheckBox object
   CCheckBox *obj=NULL;
//--- Create the specified number of CheckBox objects
   CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,count,2,2,width,DEF_CHECK_SIZE,new_column_width,autosize);
//--- In the loop by the created number of objects
   for(int i=0;i<this.ElementsTotal();i++)
     {
      //--- Get the created object from the list by the loop index
      obj=this.GetElement(i);
      //--- If the object could not be obtained, send the appropriate message to the log and move on to the next one
      if(obj==NULL)
        {
         ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_CHECKBOX));
         continue;
        }
      //--- Set the left center alignment of the checkbox and the text
      obj.SetCheckAlign(ANCHOR_LEFT);
      obj.SetTextAlign(ANCHOR_LEFT);
      //--- Set the object text
      obj.SetText("CheckBox"+string(i+1));
     }
//--- If the flag of auto resizing the base object is passed to the method,
//--- set the auto resize mode to "increase and decrease"
   if(autosize)
      this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,false);
  }
//+------------------------------------------------------------------+

In der Methode zur Erstellung eines neuen grafischen Objekts addieren wir drei Pixel zur Höhe des erstellten Objekts hinzu, das an die Methode übergeben wird, damit die CheckBox-Objekte etwas größer als der angegebene Wert sind. Wenn wir mit dem Mauszeiger über ein Objekt fahren, wird der Hintergrund des Objekts in der Liste mit Farbe gefüllt, die das gesamte Objekt abdeckt, und nicht nur seine Höhe:

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

Die Objektklasse ButtonListBox wurde in \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ButtonListBox.mqh auf ähnliche Weise verbessert.

Außerdem wurde das Flag zur automatischen Größenanpassung des Containers an seinen Inhalt hinzugefügt:

public:
//--- Create the specified number of CheckBox objects
   void              CreateButton(const int count,const int width,const int height,const int new_column_width=0,const bool autosize=true);
//--- Constructor

Im Code der Methodenimplementierung wird das Flag an die Objekterzeugungsmethode übergeben und geprüft , ob die Größe des Containers automatisch an den erzeugten Inhalt angepasst werden kann:

//+------------------------------------------------------------------+
//| Create the specified number of Button objects                    |
//+------------------------------------------------------------------+
void CButtonListBox::CreateButton(const int count,const int width,const int height,const int new_column_width=0,const bool autosize=true)
  {
//--- Create the pointer to the Button object
   CButton *obj=NULL;
//--- Create the specified number of Button objects
   CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_BUTTON,count,2,2,width,height,new_column_width,autosize);
//--- In the loop by the created number of objects
   for(int i=0;i<this.ElementsTotal();i++)
     {
      //--- Get the created object from the list by the loop index
      obj=this.GetElement(i);
      //--- If the object could not be obtained, send the appropriate message to the log and move on to the next one
      if(obj==NULL)
        {
         ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_BUTTON));
         continue;
        }
      //--- Set left center text alignment
      obj.SetTextAlign(ANCHOR_CENTER);
      //--- Set the object text
      obj.SetText("Button"+string(i+1));
     }
//--- If the flag of auto resizing the base object is passed to the method,
//--- set the auto resize mode to "increase and decrease"
   if(autosize)
      this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,false);
  }
//+------------------------------------------------------------------+

TabControl WinForms Objekt-Layout

Das ursprünglich angenommene Konzept, die Namen der grafischen Elemente in der Bibliothek zu erstellen, hindert uns daran, komplexe zusammengesetzte grafische Objekte zu erstellen und neue Objekte an bereits erstellte Objekte in der wachsenden Hierarchie ihrer Verschachtelung anzuhängen (dies wird später korrigiert). Daher werde ich hier nur das Layout des TabControls erstellen, um zu verstehen, wie wir es nach Anwendung des neuen Konzepts der Namensgebung für grafische Elemente implementieren können.

Das TabControl-Objekt sollte aus einem Panel bestehen, das die Registerkarten (TabPage-Objekte) enthält, die aus einer Schaltfläche und einem Panel bestehen. Die Schaltfläche (Registerkartenkopf - TabHeader) aktiviert die Registerkarte, während das Panel die Objekte enthält, die sich auf der Registerkarte befinden sollen. Wenn eine Registerkarte aktiviert ist und ihr Bereich angezeigt wird, wird die Schaltfläche etwas größer als die Schaltflächen der inaktiven Registerkarten, deren Bereiche ausgeblendet sind.

Da die Registerkartenüberschrift (das TabHeader-Objekt) wie eine Schaltfläche funktionieren soll und dabei größer werden kann, und sich ihr Aussehen leicht vom Button-Steuerelement unterscheidet, sollte dieses Objekt dementsprechend ein Nachkomme des Button-Steuerelements sein, in dem zusätzliche Funktionalität implementiert ist.

Das Tab-Objekt (TabPage) sollte ein Container-Objekt sein, da es sowohl über eine Titel-Schaltfläche verfügen als auch andere Objekte daran anhängen muss. Höchstwahrscheinlich wird es das Panel-Objekt sein, an das die Titeltaste angehängt wird, während die angehängten Elemente auf dem Panel selbst platziert werden können.

Das TabControl-Objekt sollte ein Panel-Objekt sein, an das Registerkartenobjekte angehängt sind.

Dies ist jedoch alles nur eine Theorie. Da wir in der Praxis durch die Anzahl der verschachtelten Objekte begrenzt sind und kein vollwertiges Objekt erstellen können, an das wir anschließend andere Objekte anhängen, beschränken wir uns darauf, leere Klassen von TabHeader- und TabPage-Objekten zu erstellen, während das Objekt selbst (oder vielmehr sein Layout-Prototyp) aus dem Container-Objekt erstellt wird. Drei Schaltflächen und drei Container zur Visualisierung des Erscheinungsbildes des TabControls sind ebenfalls daran angehängt.

Das Objekt ist leer und statisch, aber die Schaltflächen reagieren auf Mausklicks und wenn man mit der Maus darüber fährt. Aber die aktive Registerkarte wird sich nicht vergrößern, wenn die Schaltfläche gedrückt wird, und die inaktiven Registerkarten werden nicht verkleinert, da wir immer noch keine solche Funktion für Schaltflächen haben. Ich werde damit beginnen, all dies ab dem nächsten Artikel zu implementieren, nachdem wir ein neues Konzept für die Benennung der grafischen Elemente der Bibliothek erstellt haben.

In \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\ erstellen wir die Datei TabControl.mqh mi der Klasse TabControl.

Binden wir die für den Betrieb der Klassen erforderlichen Dateien ein:

//+------------------------------------------------------------------+
//|                                                   TabControl.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\Container.mqh"
#include "..\Containers\GroupBox.mqh"
//+------------------------------------------------------------------+

Fügen wir unten Dummy-Klassen hinzu. Sie dienen als Rohlinge zweier Klassen für die Erstellung der Registerkartenüberschrift und der Registerkarte selbst:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\Containers\Container.mqh"
#include "..\Containers\GroupBox.mqh"
//+------------------------------------------------------------------+
//| TabHeader object class of WForms TabControl                      |
//+------------------------------------------------------------------+
class CTabHeader : public CButton
  {
private:

protected:

public:

//--- Constructor
                     CTabHeader(const long chart_id,
                             const int subwindow,
                             const string name,
                             const int x,
                             const int y,
                             const int w,
                             const int h);
  };
//+------------------------------------------------------------------+
//| CTabHeader::Constructor                                          |
//+------------------------------------------------------------------+
CTabHeader::CTabHeader(const long chart_id,
                       const int subwindow,
                       const string name,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CButton(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| TabPage object class of WForms TabControl                        |
//+------------------------------------------------------------------+
class CTabPage : public CContainer
  {
private:

public:

//--- Constructor
                     CTabPage(const long chart_id,
                              const int subwindow,
                              const string name,
                              const int x,
                              const int y,
                              const int w,
                              const int h);
  };
//+------------------------------------------------------------------+
//| CTabPage::Constructor indicating the chart and subwindow ID      |
//+------------------------------------------------------------------+
CTabPage::CTabPage(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_TAB_PAGE);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_TAB_PAGE);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
  }
//+------------------------------------------------------------------+

Da die Klassen nur die minimal notwendigen parametrischen Konstruktoren implementieren, die lediglich den Typ des grafischen Elements und den Typ des grafischen Objekts der Bibliothek angeben, gibt es hier nichts zu beachten. Ich werde die Klassen im nächsten Artikel implementieren.

Als Nächstes deklarieren wir die WinForms-Objektklasse TabControl , die von der Container-Objektklasse abgeleitet wurde und platzieren darin die Deklarationen von Variablen und Methoden für die Handhabung der Klasse:

//+------------------------------------------------------------------+
//| TabControl object class of WForms controls                       |
//+------------------------------------------------------------------+
class CTabControl : public CContainer
  {
private:
   int                  m_item_width;                 // Fixed width of tab titles
   int                  m_item_height;                // Fixed height of tab titles
//--- 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 TabPage objects
   void              CreateTabPage(const int count,const int width,const int height,const int tab_state=1);

//--- (1) Set and (2) return the location of tab headers on the control
   void              SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment)   { this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment); }
   ENUM_CANV_ELEMENT_ALIGNMENT Alignment(void)  const { return (ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT);  }
//--- (1) Set and (2) return the flag allowing multiple rows of tab headers on the control
   void              SetMultiline(const bool flag)    { this.SetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE,flag);                                 }
   bool              Multiline(void)            const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE);                         }
//--- (1) Set and (2) return the fixed width of tab headers
   void              SetItemWidth(const int value)    { this.m_item_width=value;    }
   int               ItemWidth(void)            const { return this.m_item_width;   }
//--- (1) Set and (2) return the fixed height of tab headers
   void              SetItemHeight(const int value)   { this.m_item_height=value;   }
   int               ItemHeight(void)           const { return this.m_item_height;  }
//--- Set the fixed size of tab headers
   void              SetItemSize(const int w,const int h)
                       {
                        if(this.ItemWidth()!=w)
                           this.SetItemWidth(w);
                        if(this.ItemHeight()!=h)
                           this.SetItemHeight(h);
                       }
//--- Constructor
                     CTabControl(const long chart_id,
                                 const int subwindow,
                                 const string name,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h);
  };
//+------------------------------------------------------------------+

Geben wir im Klassenkonstruktor den Typ des grafischen Elements und den Typ des Bibliotheksobjekts an, und legen die Standardwerte für die Eigenschaften und Farben des Objekts fest:

//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CTabControl::CTabControl(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_TAB_CONTROL);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL);
   this.SetID(this.GetMaxIDAll()+1);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   this.SetBorderSizeAll(0);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetOpacity(CLR_DEF_CONTROL_TAB_OPACITY,true);
   this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_BACK_COLOR,true);
   this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_MOUSE_DOWN);
   this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_MOUSE_OVER);
   this.SetBorderColor(CLR_DEF_CONTROL_TAB_BORDER_COLOR,true);
   this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_BORDER_MOUSE_DOWN);
   this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_BORDER_MOUSE_OVER);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetMultiline(false);
   this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP);
   this.SetItemSize(58,20);
   this.CreateTabPage(3,this.Width(),this.Height()-this.ItemHeight(),0);
  }
//+------------------------------------------------------------------+

Am Ende des Consturctor-Codes rufen wir die Methode zur Erstellung von drei Registerkarten auf.

Die private virtuelle Methode, die ein neues grafisches Objekt erstellt:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CTabControl::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_LIST_BOX  :
         element=new CListBox(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;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX   :
         element=new CButtonListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER       :
         element=new CTabHeader(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_PAGE       :
         element=new CTabPage(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
         element=new CTabControl(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;
  }
//+------------------------------------------------------------------+

Die Methode ist identisch mit genau den gleichen Methoden zur Erstellung von grafischen Elementen in anderen Klassen von Containerobjekten und erstellt einfach neue grafische Elemente entsprechend dem Typ, der der Methode übergeben wird.

Die Methode, die die angegebene Anzahl von TabPage-Objekten erzeugt:

//+------------------------------------------------------------------+
//| Create the specified number of TabPage objects                   |
//+------------------------------------------------------------------+
void CTabControl::CreateTabPage(const int count,const int width,const int height,const int tab_state=1)
  {
//--- Create the pointer to the Button and Container objects
   CButton *header=NULL;
   CContainer *tab=NULL;
//--- Create the specified number of TabPage objects
   for(int i=0;i<count;i++)
     {
      //--- Set the initial tab coordinates
      int x=this.BorderSizeLeft()+2;
      int y=this.BorderSizeTop()+2;
      //--- Create the button object as a tab header
      if(!CContainer::CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON,x+(this.ItemWidth()-4)*i,y,this.ItemWidth()-4,this.ItemHeight()-2,clrNONE,255,true,false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_BUTTON));
         continue;
        }
      //--- Get the created button from the list of created objects
      header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,i);
      if(header==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_BUTTON));
         continue;
        }
      //--- Set the header default property values
      header.SetID(this.GetMaxIDAll()+1);
      header.SetToggleFlag(true);
      header.SetGroupButtonFlag(true);
      header.SetText("TabPage"+string(i+1));
      header.SetTextAlign(ANCHOR_CENTER);
      header.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true);
      header.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true);
      header.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN);
      header.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER);
      header.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true);
      header.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON);
      header.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON);
      header.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
      header.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN);
      header.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER);
      header.SetForeColor(CLR_DEF_FORE_COLOR,true);
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP)
         header.SetBorderSize(1,1,1,0);
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM)
         header.SetBorderSize(1,0,1,1);
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT)
         header.SetBorderSize(1,1,0,1);
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT)
         header.SetBorderSize(0,1,1,1);
      
      //--- Create a container object as a tab field attached objects are to be located on
      if(!CContainer::CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CONTAINER,x-2,header.BottomEdgeRelative(),width,height,clrNONE,255,true,false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_CONTAINER));
         continue;
        }
      //--- Get the tab from the list of created objects
      tab=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,i);
      if(tab==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_CONTAINER));
         continue;
        }
      //--- Set the default property values for the created tab
      tab.SetID(this.GetMaxIDAll()+1);
      tab.SetBorderSizeAll(1);
      tab.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true);
      tab.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true);
      tab.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
      tab.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
      tab.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
      tab.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
      tab.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
      tab.SetForeColor(CLR_DEF_FORE_COLOR,true);
      tab.Hide();
     }
   //--- Get the title and tab from the list by the index of the active tab
   header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,tab_state);
   tab=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,tab_state);
   //--- If the pointers to objects have been received
   if(header!=NULL && tab!=NULL)
     {
      //--- Display the tab
      tab.Show();
      //--- Move the title to the front and set new sizes for it
      header.BringToTop();
      header.SetState(true);
      header.SetWidth(this.ItemWidth());
      header.SetHeight(this.ItemHeight());
      //--- Shift the title to new coordinates, since the size has become slightly larger,
      //--- and set new relative coordinates for the header
      header.Move(header.CoordX()-2,header.CoordY()-2);
      header.SetCoordXRelative(header.CoordXRelative()-2);
      header.SetCoordYRelative(header.CoordYRelative()-2);
      header.Update(true);
     }
  }
//+------------------------------------------------------------------+

Da es sich hierbei um eine vorläufige Methode handelt, die nur dazu dient, das Konzept der Erstellung von Registerkarten zu testen, werden wir sie nicht allzu sehr berücksichtigen, da sie im nächsten Artikel noch erhebliche Änderungen erfahren wird. Kurz gesagt, die angegebene Anzahl von Schaltflächenobjekten wird in der Schleife erstellt. Die Größe der Schaltflächenobjekte ist kleiner als die für den Titel der aktiven Registerkarte erforderliche Größe. Anschließend wird ein Containerobjekt als Registerkartenfeld erstellt, das die mit der Registerkarte verbundenen Objekte aufnimmt, und das Objekt wird sofort ausgeblendet. Nach ihrer Erstellung erhalten beide Objekte Standardwerte für ihre Eigenschaften, und der Titel der aktiven Registerkarte, deren Nummer in den Eingaben der Methode angegeben ist, wird aktiv — die Schaltfläche erhält den Status „gedrückt“, ihre Größe erhöht sich auf die in den Eigenschaften angegebene Größe, und sie wird in den Vordergrund gebracht, während das Registerfeld angezeigt wird.

Dies ist alles, was wir brauchen, um das Layout des TabControls anzuzeigen.

Damit wir TabControl-Steuerelemente in Container-Objekten erstellen können, werden wir Änderungen an den Klassen der Container-Objekte vornehmen.

In der Container-Objektklasse \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh, und zwar in der Methode, die die Parameter für das angehängte Objekt setzt, fügen wir das Setzen der Eigenschaftswerte für die erstellten Objekte TabHeader, TabPage und TabControl hinzu:

//+------------------------------------------------------------------+
//| Set parameters for the attached object                           |
//+------------------------------------------------------------------+
void CContainer::SetObjParams(CWinFormBase *obj,const color colour)
  {
//--- Set the text color of the 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 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 "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            :
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
        //--- 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 "ListBox", "CheckedListBox" and "ButtonListBox" WinForms object
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX          :
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX  :
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_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;
      case GRAPH_ELEMENT_TYPE_WF_TAB_PAGE          :
        //--- 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_TAB_PAGE_BACK_COLOR : colour,true);
        obj.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
        obj.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
        obj.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
        obj.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
        obj.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        obj.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY);
        obj.SetBorderSizeAll(1);
        obj.SetBorderStyle(FRAME_STYLE_NONE);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
        //--- 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_TAB_BACK_COLOR : colour,true);
        obj.SetBorderColor(CLR_DEF_CONTROL_TAB_BORDER_COLOR,true);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        obj.SetOpacity(CLR_DEF_CONTROL_TAB_OPACITY);
        break;
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

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 "TabControl.mqh"
#include "..\..\WForms\Common Controls\ListBox.mqh"
#include "..\..\WForms\Common Controls\CheckedListBox.mqh"
#include "..\..\WForms\Common Controls\ButtonListBox.mqh"
//+------------------------------------------------------------------+

Diese Klasse wird nun für alle Container-Objektklassen sichtbar sein.

In der Methode zur Erstellung eines neuen grafischen Objekts fügen wir das die Erstellen der Objekte der Klasse TabControl 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_LIST_BOX          :
         element=new CListBox(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;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX   :
         element=new CButtonListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
         element=new CTabHeader(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_PAGE          :
         element=new CTabPage(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
         element=new CTabControl(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 ist alles Standard für solche Methoden. Je nach dem Typ, der der Methode übergeben wird, wird das entsprechende Objekt erstellt.

In der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBoxGroupBox.mqh der Objektklasse GroupBox fügen wir das Erstellen von Objekten der Klasse TabControl hinzu, ähnlich wie es in der Methode zur Erstellung eines neuen grafischen Objekts gemacht wurde:

//+------------------------------------------------------------------+
//| 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_LIST_BOX  :
         element=new CListBox(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;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX   :
         element=new CButtonListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
         element=new CTabHeader(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_PAGE          :
         element=new CTabPage(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
         element=new CTabControl(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;
  }
//+------------------------------------------------------------------+

In der Datei \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh, der Klasse der grafischen Elementkollektion, und zwar in der Methode zur Nachbearbeitung des ehemals aktiven Formulars unter dem Cursor, implementieren wir die Übergabe des Zeigers auf das aktuelle Formular (über dem sich der Cursor befindet) in die Methode als neuen formalen Parameter, ebenso wie die Parameter der Ereignisbehandlung:

//--- Reset all interaction flags for all forms except the specified one
   void              ResetAllInteractionExeptOne(CGCnvElement *form);
//--- Post-processing of the former active form under the cursor
   void              FormPostProcessing(CForm *form,const int id, const long &lparam, const double &dparam, const string &sparam);
//--- Add the element to the collection list

In der Methode selbst erhalten wir das Hauptobjekt, an das das Formular angehängt ist. Dann erhalten wir eine Liste aller Objekte, die mit dem Formular verbunden sind. Als Nächstes wird in der Schleife durch die erhaltene Liste jedes nächste angehängte Objekt abgerufen. Wir stellen auch sicher, die Methode zur Bestimmung der Position des Mauszeigers relativ zum Objekt aufzurufen. Dies ist der Hauptgrund dafür, dass Objekte ihre Farbe nicht ändern, wenn der Cursor von ihnen wegbewegt wird. In einigen Fällen war dieser Zustand nicht im Objekt angegeben und konnte nicht verarbeitet werden:

//+------------------------------------------------------------------+
//| Post-processing of the former active form under the cursor       |
//+------------------------------------------------------------------+
void CGraphElementsCollection::FormPostProcessing(CForm *form,const int id, const long &lparam, const double &dparam, const string &sparam)
  {
//--- Get the main object the form is attached to
   CForm *main=form.GetMain();
   if(main==NULL)
      main=form;
//--- Get all the elements attached to the form
   CArrayObj *list=main.GetListElements();
   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
         CWinFormBase *elm=obj.GetInteractForm(j);
         if(elm==NULL)
            continue;
         //--- determine the location of the cursor relative to the object 
         //--- and call the mouse event handling method for the object
         elm.MouseFormState(id,lparam,dparam,sparam);
         elm.OnMouseEventPostProcessing();
        }
     }
   ::ChartRedraw(main.ChartID());
  }
//+------------------------------------------------------------------+

Nach dem Ende der Schleife wird das Chart aktualisiert. Nach dieser Verfeinerung sollte das Objekt korrekt mit der Maus interagieren.

In der Ereignisbehandlung werden nun der Zeiger auf das aktuelle Formular und die Werte der Handler-Parameter an die Methode übergeben:

            else
              {
               //--- The undefined mouse status in mouse_state means releasing the left button
               //--- Assign the new mouse status to the variable
               if(mouse_state==MOUSE_FORM_STATE_NONE)
                  mouse_state=MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED;
               //--- Handle moving the cursor mouse away from the graphical element
               this.FormPostProcessing(form,id,lparam,dparam,sparam);
              }

Alles ist bereit für einen Test.


Test

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

Anstatt die Objekte CheckedListBox, ButtonListBox und ListBox auf dem zweiten GroupBox-Steuerelement zu erstellen, erstellen wir das Objekt TabControl:

      //--- 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 TabControl object
            gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,4,12,gbox2.Width()-12,gbox2.Height()-20,clrNONE,255,true,false);
            //--- get the pointer to the TabControl object by its index in the list of bound objects of the TabControl type
            CTabControl *tctrl=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0);
            if(tctrl!=NULL)
              {
               //--- get the pointer to the Container object by its index in the list of bound objects of the Container type
               CContainer *page=tctrl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,0);
               if(page!=NULL)
                 {
                  // Here we will create objects attached to the specified tab of the TabControl object
                  // Unfortunately, in the current state of creating the names of graphical objects of the library,
                  // their further creation is limited by the number of characters in the resource name in the CCanvas class
                 }
               
              }
            /*
            //--- Create the CheckedListBox object
            //---...
            //---...
           }

Hier erstellen wir einfach ein Objekt der Klasse CTabControl. Danach können wir nichts mehr damit machen, denn wenn wir versuchen, ein anderes Objekt damit zu verbinden, wird die Bibliothek mit einem Fehler reagieren, weil die Länge des Namens der grafischen Ressource überschritten wurde. Ich werde dies im nächsten Artikel korrigieren.

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


Im linken Teil des erstellten Panels sehen wir die korrekte Verarbeitung der Interaktion von Objekten mit der Maus. Auf der rechten Seite sehen wir das Layout des zukünftigen TabControl-Steuerelements. Es zeigt, dass die erste Registerkarte aktiv ist, und die Größe des Titels ist etwas größer als die Größe der Titel der inaktiven Registerkarten. Die Registerkarten reagieren auf Mausinteraktionen, genauer gesagt auf das Vorhandensein des Cursors im Titelbereich und auf das Drücken von Schaltflächen. Es gibt keine andere Funktion, und sie wird hier auch nicht benötigt. Ich habe lediglich einen Kontrollprototyp erstellt. Ich werde mich in den folgenden Artikeln mit ihrem Inhalt befassen.


Was kommt als Nächstes?

Im nächsten Artikel werde ich einen neuen Algorithmus für die Benennung von grafischen Bibliotheksobjekten erstellen und die Entwicklung des WinForms-Objekts TabControl fortsetzen.

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
DoEasy. Steuerung (Teil 11): WinForms Objekte — Gruppen, das WinForms-Objekt CheckedListBox
DoEasy. Steuerung (Teil 12): Basislistenobjekt, ListBox und ButtonListBox WinForms-Objekte



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

Beigefügte Dateien |
MQL5.zip (4412.44 KB)
Neuronale Netze leicht gemacht (Teil 23): Aufbau eines Tools für Transfer Learning Neuronale Netze leicht gemacht (Teil 23): Aufbau eines Tools für Transfer Learning
In dieser Artikelserie haben wir bereits mehr als einmal über Transfer Learning berichtet. In diesem Artikel schlage ich vor, diese Lücke zu schließen und einen genaueren Blick auf Transfer Learning zu werfen.
Der Indikator CCI: Drei Transformationsschritte Der Indikator CCI: Drei Transformationsschritte
In diesem Artikel werde ich zusätzliche Änderungen am CCI vornehmen, die die eigentliche Logik dieses Indikators betreffen. Außerdem können wir sie im Hauptfenster des Charts sehen.
DoEasy. Steuerung (Teil 14): Neuer Algorithmus zur Benennung von grafischen Elementen. Fortsetzung der Arbeit am TabControl WinForms Objekt DoEasy. Steuerung (Teil 14): Neuer Algorithmus zur Benennung von grafischen Elementen. Fortsetzung der Arbeit am TabControl WinForms Objekt
In diesem Artikel werde ich einen neuen Algorithmus für die Benennung aller grafischen Elemente erstellen, die für die Erstellung von nutzerdefinierten Grafiken gedacht sind, sowie die Entwicklung des TabControl WinForms Objekts fortsetzen.
Neuronale Netze leicht gemacht (Teil 22): Unüberwachtes Lernen von rekurrenten Modellen Neuronale Netze leicht gemacht (Teil 22): Unüberwachtes Lernen von rekurrenten Modellen
Wir untersuchen weiterhin Modelle und Algorithmen für unüberwachtes Lernen. Diesmal schlage ich vor, dass wir die Eigenschaften von AutoAutoencodern bei der Anwendung auf das Training rekurrenter Modelle diskutieren.