DoEasy. Steuerung (Teil 18): Funktionsweise für scrollende Registerkarten in TabControl

Artyom Trishkin | 10 November, 2022

Inhalt


Konzept

Im nächsten Artikel werde ich meine Arbeit an dem TabControl WinForms Objekt fortsetzen. Wenn wir uns die Bedienung des Steuerelements in MS Visual Studio ansehen, können wir folgendes Verhalten erkennen: Wenn die in einer Zeile angeordnete Kopfzeile nicht in die Breite des Steuerelements passt, werden die Kopfzeilen an den Rändern abgeschnitten. Wenn wir eine Kopfzeile auswählen, die vom rechten Rand des Steuerelements abgeschnitten ist, wird die gesamte Kopfzeile nach links verschoben, sodass die ausgewählte Kopfzeile in die Breite des Containers passt, d. h. auf der linken Seite geht die erste zuvor sichtbare Kopfzeile über den linken Rand des Steuerelements hinaus und die darauf folgende Kopfzeile wird sichtbar. Das Scrollen der Kopfleiste funktioniert auf die gleiche Weise, wenn Sie auf die Schaltflächen für den Versatz klicken, die erscheinen, wenn die Kopfleiste nicht in die Größe des Steuerelements passt.

In diesem Artikel werde ich das gleiche Verhalten für das TabControl-Objekt implementieren. Ich werde das Objekt nicht mit Schaltflächen verschieben, sondern mich darauf beschränken, die Kopfzeile nach links zu verschieben, wenn die Kopfzeile nach rechts abgeschnitten wird, wenn sich die Kopfzeilen über oder unter dem Steuerelement befinden. Dies wird ein erster Test dieser Funktionalität sein. Im nächsten Artikel werde ich darauf aufbauend vollwertige Methoden erstellen, um alle Überschriften auf jeder Seite des Steuerelements zu scrollen, sowohl bei der Auswahl eines ausgeschnittenen Titels als auch beim Scrollen der Kopfleiste mit Steuerschaltflächen.

Im vorliegenden Artikel werde ich diese Schaltflächen an den erforderlichen Stellen platzieren, falls die Kopfleiste nicht in das Steuerelement passt. In diesem Fall, wenn die Überschriften oben oder unten platziert sind, befinden sich die Schaltflächen für die Bildlaufsteuerung oben rechts bzw. unten rechts. Wenn sich die Kopfzeilen auf der linken Seite befinden, befinden sich die Schaltflächen für die Bildlaufsteuerung oben links, und wenn sich die Kopfzeilen auf der rechten Seite befinden, befinden sich die Schaltflächen unten rechts. Die Schaltflächen für die Bildlaufsteuerung in der Kopfleiste werden um ein Pixel von der Position des Registerkartenfelds eingerückt, und die Registerkartenüberschriften werden von den Rändern dieser Schaltflächen (anstelle des Containerrahmens) mit einem Einzug von ebenfalls einem Pixel abgeschnitten. So werden diese Steuerelemente entsprechend ihrer Anordnung in TabControl von MS Visual Studio platziert.


Verbesserung der Bibliotheksklassen

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

//--- CDataPropObj
   MSG_DATA_PROP_OBJ_OUT_OF_PROP_RANGE,               // Passed property is out of object property range
   MSG_GRAPH_OBJ_FAILED_CREATE_NEW_HIST_OBJ,          // Failed to create an object of the graphical object change history
   MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_HIST_LIST,         // Failed to add the change history object to the list
   MSG_GRAPH_OBJ_FAILED_GET_HIST_OBJ,                 // Failed to receive the change history object
   MSG_GRAPH_OBJ_FAILED_INC_ARRAY_SIZE,               // Failed to increase the array size

//--- CGraphElementsCollection
   MSG_GRAPH_OBJ_FAILED_GET_ADDED_OBJ_LIST,           // Failed to get the list of newly added objects
   MSG_GRAPH_OBJ_FAILED_GET_OBJECT_NAMES,             // Failed to get object names
   MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST,         // Failed to remove a graphical object from the list
   MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_LIST,         // Failed to remove a graphical object from the list
   MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART,        // Failed to remove a graphical object from the chart
   MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_DEL_LIST,          // Failed to set a graphical object to the list of removed objects
   MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_RNM_LIST,          // Failed to set a graphical object to the list of renamed objects

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

//--- CDataPropObj
   {"Переданное свойство находится за пределами диапазона свойств объекта","The passed property is outside the range of the object's properties"},
   {"Не удалось создать объект истории изменений графического объекта","Failed to create a graphical object change history object"},
   {"Не удалось добавить объект истории изменений в список","Failed to add change history object to the list"},
   {"Не удалось получить объект истории изменений","Failed to get change history object"},
   {"Не удалось увеличить размер массива","Failed to increase array size"},
   
//--- CGraphElementsCollection
   {"Не удалось получить список вновь добавленных объектов","Failed to get the list of newly added objects"},
   {"Не удалось получить имена объектов","Failed to get object names"},
   {"Не удалось изъять графический объект из списка","Failed to detach graphic object from the list"},
   {"Не удалось удалить графический объект из списка","Failed to delete graphic object from the list"},
   {"Не удалось удалить графический объект с графика","Failed to delete graphic object from the chart"},
   {"Не удалось поместить графический объект в список удалённых объектов","Failed to place graphic object in the list of deleted objects"},
   {"Не удалось поместить графический объект в список переименованных объектов","Failed to place graphic object in the list of renamed objects"},


Wenn wir auf Steuerelemente klicken, können wir dieses Ereignis nicht immer in derselben Klasse behandeln. Es kann Situationen geben, in denen wir die Funktionalität von anderen Klassen aufrufen müssen, die in der Klasse mit dem Mausereignishandler für ein angeklicktes Objekt nicht verfügbar sind. Die hier vorgeschlagene Lösung sieht folgendermaßen aus: Wenn das Steuerelement angeklickt wird, senden wir ein Ereignis an das Chart des Steuerprogramms, und die Bibliothek verarbeitet dieses Ereignis, indem sie es an die Klasse sendet, in der sich die Funktionalität zur Verarbeitung dieses Ereignisses befindet.

Auf diese Weise werden einige interne Ereignisbehandlungen implementiert. Aber zusätzlich zu den Anforderungen der Bibliothek müssen wir noch einige Ereignisse an das Steuerprogramm senden, damit wir sie von dort aus verarbeiten können. Daher müssen wir in jedem Fall das Ereignismodell verwenden, um die Verarbeitung von Ereignissen grafischer Elemente nicht über den Timer zu organisieren.

In \MQL5\Include\DoEasy\Defines.mqh fügen wir die Enumeration der möglichen Ereignisse der WinForms-Objekte der Bibliothek hinzu:

//+------------------------------------------------------------------+
//| List of possible WinForms control events                         |
//+------------------------------------------------------------------+
enum ENUM_WF_CONTROL_EVENT
  {
   WF_CONTROL_EVENT_NO_EVENT = GRAPH_OBJ_EVENTS_NEXT_CODE,// No event
   WF_CONTROL_EVENT_CLICK,                            // "Click on the control" event
   WF_CONTROL_EVENT_TAB_SELECT,                       // "TabControl tab selection" event
  };
#define WF_CONTROL_EVENTS_NEXT_CODE (WF_CONTROL_EVENT_TAB_SELECT+1)  // The code of the next event after the last graphical element event code
//+------------------------------------------------------------------+
//| Mode of automatic interface element resizing                     |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_AUTO_SIZE_MODE
  {
   CANV_ELEMENT_AUTO_SIZE_MODE_GROW,                  // Increase only
   CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,           // Increase and decrease
  };
//+------------------------------------------------------------------+

Bisher gibt es in der Enumeration nur zwei Ereignisse: Klicken auf das Steuerelement und Auswählen der Registerkarte in TabControl. Letztere werde ich für die Behandlung des Klicks auf die abgeschnittene Registerkartenüberschrift verwenden, um das Scrollen der Registerkartenüberschriftszeile anzuordnen.

Zuvor habe ich Hilfssteuerelemente erstellt, die keine unabhängigen WinForms-Objekte sind, sondern zur Erstellung anderer Steuerelemente verwendet werden. Sie wurden alle im gemeinsamen Ordner der WinForms-Objekte abgelegt. Da immer mehr von ihnen auftauchen, werde ich für sie einen separaten Ordner namens „Helpers“ in \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ erstellen und alle Dateien der WinForms-Hilfsobjekte dorthin verschieben, wie ArrowButton.mqh, ArrowDownButton.mqh, ArrowLeftButton.mqh, ArrowLeftRightBox.mqh, ArrowRightButton.mqh, ArrowUpButton.mqh, ArrowUpDownBox.mqh, ListBoxItem.mqh, TabField.mqh und TabHeader.mqh.

Da die Dateien der Hilfsobjekte jetzt einen neuen Pfad haben, müssen wir die Pfadangaben der eingeschlossenen Dateien in einigen Bibliotheksdateien korrigieren.

In \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ListBoxItem.mqh bearbeiten wir den Pfad:

//+------------------------------------------------------------------+
//|                                                  ListBoxItem.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 "..\Common Controls\Button.mqh"
//+------------------------------------------------------------------+
//| Label object class of WForms controls                            |
//+------------------------------------------------------------------+


In \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ArrowButton.mqh:

//+------------------------------------------------------------------+
//|                                                  ArrowButton.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 "..\Common Controls\Button.mqh"
//+------------------------------------------------------------------+
//| Arrow Button object class of WForms controls                     |
//+------------------------------------------------------------------+


In \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ArrowLeftRightBox.mqh:

//+------------------------------------------------------------------+
//|                                            ArrowLeftRightBox.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\Containers\Panel.mqh"
//+------------------------------------------------------------------+
//| ArrowLeftRightBox object class of WForms controls                |
//+------------------------------------------------------------------+


In \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ArrowUpDownBox.mqh:

//+------------------------------------------------------------------+
//|                                               ArrowUpDownBox.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\Containers\Panel.mqh"
//+------------------------------------------------------------------+
//| ArrowUpDownBox object class of the WForms controls               |
//+------------------------------------------------------------------+


In \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ButtonListBox.mqh,

//+------------------------------------------------------------------+
//|                                              ElementsListBox.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 "..\Helpers\ListBoxItem.mqh"
//+------------------------------------------------------------------+
//| Class of the base object of the WForms control list              |
//+------------------------------------------------------------------+


In \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh passen wir die include-Befehle für die Dateien an, die sich jetzt im neuen Ordner befinden:

//+------------------------------------------------------------------+
//|                                                        Panel.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 "Container.mqh"
#include "..\Helpers\TabField.mqh"
#include "..\Helpers\ArrowButton.mqh"
#include "..\Helpers\ArrowUpButton.mqh"
#include "..\Helpers\ArrowDownButton.mqh"
#include "..\Helpers\ArrowLeftButton.mqh"
#include "..\Helpers\ArrowRightButton.mqh"
#include "..\Helpers\ArrowUpDownBox.mqh"
#include "..\Helpers\ArrowLeftRightBox.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"
//+------------------------------------------------------------------+
//| Panel object class of WForms controls                            |
//+------------------------------------------------------------------+

und ändern die Methode CreateNewGObject() — in ‚switch‘ ordnen wir einfach alle Zeilen in ihren Fällen in jeweils einer an. Dadurch wird die Methode kleiner und leichter zu lesen:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                       const int obj_num,
                                       const string descript,
                                       const int x,
                                       const int y,
                                       const int w,
                                       const int h,
                                       const color colour,
                                       const uchar opacity,
                                       const bool movable,
                                       const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT                 : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break;
      case GRAPH_ELEMENT_TYPE_FORM                    : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h);              break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL                : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL                : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX             : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON          : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON               : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX             : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM        : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX     : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX      : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER           : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD            : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL          : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);  break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      default  : break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+

Zuvor sah die Methode folgendermaßen aus:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                       const int obj_num,
                                       const string descript,
                                       const int x,
                                       const int y,
                                       const int w,
                                       const int h,
                                       const color colour,
                                       const uchar opacity,
                                       const bool movable,
                                       const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT :
         element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity);
        break;
      case GRAPH_ELEMENT_TYPE_FORM :
         element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            :
         element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             :
         element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL                :
         element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL                :
         element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX             :
         element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON          :
         element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON               :
         element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX             :
         element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM        :
         element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX     :
         element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX      :
         element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER           :
         element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD            :
         element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL          :
         element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         :
         element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      :
         element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    :
         element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    :
         element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   :
         element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX :
         element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX :
         element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+

Eine solche Formatierung verhindert, dass die gesamte Methode auf einmal ausgewertet wird, was für den Vergleich der gleichen Methoden in anderen Klassen, in denen ich sie aus Gründen der Klarheit des Vergleichs auf die gleiche Weise ändern werde, weniger praktisch ist. Wozu ist das alles gut? Ich sehe, dass diese Methode in verschiedenen Containerklassen die gleiche ist, und dementsprechend muss sie so organisiert werden, dass sie in allen Klassen, in denen sie existiert, die einzige ist. Gleichzeitig bleiben alle Klassen der darin erstellten Objekte von diesem Ort aus zugänglich. Ich werde dies in den folgenden Artikeln berücksichtigen.


In \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\TabField.mqh passen wir die Zeilen zum Einbinden der Panel-Objektklassendatei an:

//+------------------------------------------------------------------+
//|                                                     TabField.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\Containers\Panel.mqh"
//+------------------------------------------------------------------+
//| TabHeader object class of WForms TabControl                      |
//+------------------------------------------------------------------+

und ändern die Formatierung der Methode CreateNewGObject():

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CTabField::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int obj_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT                 : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break;
      case GRAPH_ELEMENT_TYPE_FORM                    : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL                : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL                : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX             : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON          : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON               : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX             : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM        : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX     : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX      : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER           : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD            : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL          : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      default  : break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+


In \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh ändern wir auch die Formatierung:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CGroupBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int obj_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT                 : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break;
      case GRAPH_ELEMENT_TYPE_FORM                    : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h);              break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL                : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL                : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX             : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON          : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON               : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX             : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM        : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX     : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX      : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER           : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD            : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL          : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);  break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      default  : break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+

Nach solchen Änderungen in allen Containerklassendateien wurden die Methoden besser lesbar, und jetzt können sie alle auf einen Blick miteinander verglichen werden. Sie sind gleich. Das bedeutet, dass wir eine gemeinsame Methode oder Funktion erstellen und diese in den Methoden aller Containerklassen aufrufen müssen.


Wenn ich auf eine Registerkartenüberschrift klicke, die nicht vollständig außerhalb des Containers liegt und teilweise abgeschnitten ist, muss ich die Kopfleiste verschieben, damit die Überschrift vollständig sichtbar ist. Der Versatz der Kopfleiste erfolgt in der TabControl-Klasse, die in der Tabulator-Kopfzeilen-Objektklasse nicht sichtbar ist, da sich die Registerkarten innerhalb des TabControl-Objekts befinden. Daher müssen wir eine Ereignismeldung senden, die den Index der angeklickten Kopfzeile und die Namen der Objekte aufzeichnet, in denen das Ereignis aufgetreten ist. Wir benötigen die Namen sowohl des TabControl-Objekts als auch des Objekts, an das es angehängt ist. Dies sind die Namen des Hauptobjekts und des TabControl-Objekts. Wenn wir diese Namen kennen, können wir das Hauptobjekt, an das alle anderen Objekte angehängt sind, einschließlich TabControl (dessen Name uns bereits bekannt sein wird), im Ereignis-Handler der Bibliothek genau identifizieren. Dies ermöglicht es uns, das Objekt auszuwählen, wenn mehrere TabControl-Objekte an das Hauptobjekt angehängt sind.

Alle Objekte desselben Typs werden jetzt in der Bibliothek mit unterschiedlichen Namen erstellt, unabhängig davon, auf welchem Chart sie erstellt werden. Um nach einem Objekt zu suchen, müssen wir also nur seinen Namen kennen. Alle Methoden zur Auswahl eines Objekts nach Namen, die derzeit in der Bibliothek verfügbar sind, erfordern auch die ID des Charts, auf dem sie erstellt wurden. Da die Chart-ID nicht erforderlich ist, um ein Objekt nach seinem Namen auszuwählen (dies gilt nur für diese Bibliothek, da das Client-Terminal die Erstellung von Objekten mit demselben Namen, aber in verschiedenen Charts erlaubt), und wir noch keine solche Methode haben, sollten wir sie erstellen.

In \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh deklarieren wir eine Methode, die ein grafisches Element anhand seines Namens zurückgibt:

public:
//--- Draw a frame
   virtual void      DrawFrame(void){}
//--- Return by type the (1) list, (2) the number of bound controls, the bound control (3) by index in the list, (4) by name
   CArrayObj        *GetListElementsByType(const ENUM_GRAPH_ELEMENT_TYPE type);
   int               ElementsTotalByType(const ENUM_GRAPH_ELEMENT_TYPE type);
   CGCnvElement     *GetElementByType(const ENUM_GRAPH_ELEMENT_TYPE type,const int index);
   CGCnvElement     *GetElementByName(const string name);
//--- Clear the element filling it with color and opacity

und schreiben ihre Implementierung außerhalb des Klassenkörper:

//+------------------------------------------------------------------+
//| Return the bound element by name                                 |
//+------------------------------------------------------------------+
CGCnvElement *CWinFormBase::GetElementByName(const string name)
  {
   string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
   CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListElements(),CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
   return(list!=NULL ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

Hier wird zunächst geprüft, ob der Name des gesuchten Objekts den Namen des Programms enthält, und wenn dieser nicht in dem an die Methode übergebenen Namen enthalten ist, wird der Name des Programms an den Namen des gesuchten Objekts angehängt. Als Nächstes erhalten wir eine Liste der grafischen Elemente, die an das Steuerelement gebunden sind und den gewünschten Namen haben (es sollte nur ein solches Objekt geben, wenn es gefunden wird). Schließlich geben wir den Zeiger auf das einzige Objekt in der resultierenden Liste zurück. Wenn die Liste leer ist oder nicht erstellt wurde, gibt die Methode NULL zurück.

In der Objektklasse für den Registerkartenkopf müssen wir die Größe der Bildlaufsteuerelemente für die Kopfleiste kennen, die in der Objektklasse TabControl erstellt wurden. Dies ist notwendig, um die Kopfzeilen korrekt zu beschneiden, wenn die Steuerelemente sichtbar sind. Auch hier gilt, dass Kopfobjekte nichts über die Objekte wissen, die im Objekt der Klasse platziert sind, aus der sie erstellt wurden. Aber wir können alle Größen, die wir brauchen, von diesem Objekt auf die Kopfzeilenobjekte übertragen, und dann wissen die Kopfzeilen über „bestimmte“ Größen Bescheid, die beim Beschneiden des unsichtbaren Bereichs berücksichtigt werden sollten. Außerdem sollte das Sichtbarkeitsflag der Bildlaufsteuerungen für die Kopfzeilen an die Kopfzeilenobjekte gesendet werden, um zu verstehen, wann diese Größen berücksichtigt werden sollten und wann nicht.

Obwohl die Kopfzeilen also nichts über diese Objekte wissen, gibt es innerhalb der Klassen der Kopfzeilenobjekte Variablen, die alle notwendigen Informationen speichern. Wir schreiben diese Informationen in die Header-Objekte der TabControl-Objektklasse. Wir werden also die Sichtbarkeit der erforderlichen Parameter in einem Objekt emulieren, das nichts über das Objekt weiß, aber seine Parameter für die Arbeit verwendet.

In der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\TabHeader.mqh bearbeiten wir die Zeile für den Dateizugriff und deklarieren die Variablen zum Speichern der Eigenschaften der Steuerelemente für das Scrollen der Kopfzeile:

//+------------------------------------------------------------------+
//|                                                    TabHeader.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 "..\Common Controls\Button.mqh"
//+------------------------------------------------------------------+
//| TabHeader object class of WForms TabControl                      |
//+------------------------------------------------------------------+
class CTabHeader : public CButton
  {
private:
   int               m_width_off;                        // Object width in the released state
   int               m_height_off;                       // Object height in the released state
   int               m_width_on;                         // Object width in the selected state
   int               m_height_on;                        // Object height in the selected state
   bool              m_arr_butt_ud_visible_flag;         // Tab header "up-down" control buttons visibility flag 
   bool              m_arr_butt_lr_visible_flag;         // Tab header "left-right" control buttons visibility flag
   int               m_arr_butt_ud_size;                 // Tab header "up-down" control buttons size 
   int               m_arr_butt_lr_size;                 // Tab header "left-right" control buttons size
//--- Adjust the size and location of the element depending on the state


Wir schreiben im öffentlichen Teil der Klasse die Methoden für die Arbeit mit deklarierten privaten Variablen:

public:
//--- Return the visibility of the (1) left-right, (2) up-down buttons
   bool              IsVisibleLeftRightBox(void)                                 const { return this.m_arr_butt_lr_visible_flag; }
   bool              IsVisibleUpDownBox(void)                                    const { return this.m_arr_butt_ud_visible_flag; }
//--- Set the visibility of the (1) left-right, (2) up-down buttons
   void              SetVisibleLeftRightBox(const bool flag)                           { this.m_arr_butt_lr_visible_flag=flag;   }
   void              SetVisibleUpDownBox(const bool flag)                              { this.m_arr_butt_ud_visible_flag=flag;   }
//--- Set the size of the (1) left-right, (2) up-down buttons
   void              SetSizeLeftRightBox(const int value)                              { this.m_arr_butt_lr_size=value;          }
   void              SetSizeUpDownBox(const int value)                                 { this.m_arr_butt_ud_size=value;          }
//--- Find and return a pointer to the field object corresponding to the tab index

Die eigentlichen Daten zu all diesen Variablen werden in der Objektklasse TabControl gesendet. Hier werden sie verwendet, um die Größe des sichtbaren Bereichs zu berechnen und den Teil des Bildes zu beschneiden, der über den sichtbaren Bereich hinausgeht.

Um die Werte der neuen Variablen bei der Berechnung des sichtbaren Bereichs zu verwenden, müssen wir die virtuelle Methode Crop() des übergeordneten Objekts überschreiben.

Wir deklarieren die Methode im öffentlichen Bereich der Klasse:

//--- Redraw the object
   virtual void      Redraw(bool redraw);
//--- Clear the element filling it with color and opacity
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Clear the element with a gradient fill
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);

//--- Crop the image outlined by the previously specified rectangular visibility scope
   virtual void      Crop(void);

//--- Last mouse event handler
   virtual void      OnMouseEventPostProcessing(void);

Nun schreiben wir die Implementierung außerhalb des Klassenkörpers:

//+------------------------------------------------------------------+
//| Crop the image outlined by the calculated                        |
//| rectangular visibility scope                                     |
//+------------------------------------------------------------------+
void CTabHeader::Crop(void)
  {
//--- Get the pointer to the base object
   CGCnvElement *base=this.GetBase();
//--- If the object does not have a base object it is attached to, then there is no need to crop the hidden areas - leave
   if(base==NULL)
      return;
//--- Set the initial coordinates and size of the visibility scope to the entire object
   int vis_x=0;
   int vis_y=0;
   int vis_w=this.Width();
   int vis_h=this.Height();
//--- Set the size of the top, bottom, left and right areas that go beyond the container
   int crop_top=0;
   int crop_bottom=0;
   int crop_left=0;
   int crop_right=0;
//--- Get the additional size, by which to crop the titles when the arrow buttons are visible
   int add_size_lr=(this.IsVisibleLeftRightBox() ? this.m_arr_butt_lr_size : 0);
   int add_size_ud=(this.IsVisibleUpDownBox()    ? this.m_arr_butt_ud_size : 0);
//--- Calculate the boundaries of the container area, inside which the object is fully visible
   int top=fmax(base.CoordY()+(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_TOP),base.CoordYVisibleArea())+(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT ? add_size_ud : 0);
   int bottom=fmin(base.BottomEdge()-(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_BOTTOM),base.BottomEdgeVisibleArea()+1)-(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT ? add_size_ud : 0);
   int left=fmax(base.CoordX()+(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_LEFT),base.CoordXVisibleArea());
   int right=fmin(base.RightEdge()-(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_RIGHT),base.RightEdgeVisibleArea()+1)-add_size_lr;
//--- Calculate the values of the top, bottom, left and right areas, at which the object goes beyond
//--- the boundaries of the container area, inside which the object is fully visible
   crop_top=this.CoordY()-top;
   if(crop_top<0)
      vis_y=-crop_top;
   crop_bottom=bottom-this.BottomEdge()-1;
   if(crop_bottom<0)
      vis_h=this.Height()+crop_bottom-vis_y;
   crop_left=this.CoordX()-left;
   if(crop_left<0)
      vis_x=-crop_left;
   crop_right=right-this.RightEdge()-1;
   if(crop_right<0)
      vis_w=this.Width()+crop_right-vis_x;
//--- If there are areas that need to be hidden, call the cropping method with the calculated size of the object visibility scope
   if(crop_top<0 || crop_bottom<0 || crop_left<0 || crop_right<0)
      this.Crop(vis_x,vis_y,vis_w,vis_h);
  }
//+------------------------------------------------------------------+

Anders als bei der ursprünglichen Methode müssen hier zusätzliche Größen bei der Berechnung des Sichtbarkeitsbereichs berücksichtigt werden. Wenn in den Flag-Variablen angegeben ist, dass das Scroll-Control-Objekt sichtbar ist, dann sollte die Größe des sichtbaren Scroll-Controls für die Berechnung verwendet werden. Wenn die Variable angibt, dass das Objekt nicht sichtbar ist, wird anstelle der Größe der Wert Null verwendet. Die Objekte der Pfeiltasten Links-Rechts und Oben-Unten verwenden ihre eigenen Variablen und dementsprechend separate Berechnungen zusätzlicher Größen für die Berechnung des Sichtbarkeitsbereichs.

Wir initialisieren neue Variablen mit Standardwerten in den Klassenkonstruktoren:

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CTabHeader::CTabHeader(const ENUM_GRAPH_ELEMENT_TYPE type,
                       const long chart_id,
                       const int subwindow,
                       const string descript,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CButton(type,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP);
   this.SetToggleFlag(true);
   this.SetGroupButtonFlag(true);
   this.SetText(TypeGraphElementAsString(this.TypeGraphElement()));
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true);
   this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true);
   this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN);
   this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER);
   this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true);
   this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON);
   this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
   this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN);
   this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER);
   this.SetPadding(6,3,6,3);
   this.SetSizes(w,h);
   this.SetState(false);
   this.m_arr_butt_ud_visible_flag=false;
   this.m_arr_butt_lr_visible_flag=false;
   this.m_arr_butt_ud_size=0;
   this.m_arr_butt_lr_size=0;
  }
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTabHeader::CTabHeader(const long chart_id,
                       const int subwindow,
                       const string descript,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CButton(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP);
   this.SetToggleFlag(true);
   this.SetGroupButtonFlag(true);
   this.SetText(TypeGraphElementAsString(this.TypeGraphElement()));
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true);
   this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true);
   this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN);
   this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER);
   this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true);
   this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON);
   this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
   this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN);
   this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER);
   this.SetPadding(6,3,6,3);
   this.SetSizes(w,h);
   this.SetState(false);
   this.m_arr_butt_ud_visible_flag=false;
   this.m_arr_butt_lr_visible_flag=false;
   this.m_arr_butt_ud_size=0;
   this.m_arr_butt_lr_size=0;
  }
//+------------------------------------------------------------------+


Im Ereignis „Der Cursor befindet sich im aktiven Bereich, die linke Maustaste wurde geklickt“ implementieren wir den Codeblock, um das TabControl-Ereignis für die Tabulatorauswahl zu erstellen und zu senden, wenn die Kopfzeile angeklickt wird:

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| left mouse button released                                       |
//+------------------------------------------------------------------+
void CTabHeader::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);
         this.Redraw(true);
        }
      //--- 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);
         
         //--- Get the field object corresponding to the header
         CWinFormBase *field=this.GetFieldObj();
         if(field!=NULL)
           {
            //--- Display the field, bring it to the front, draw a frame and crop the excess
            field.Show();
            field.BringToTop();
            field.DrawFrame();
            field.Crop();
           }
         //--- Redraw an object and a chart
         this.Redraw(true);
        }
      //--- Send the test message to the journal
      Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.State()=",this.State(),", ID=",this.ID(),", Group=",this.Group());
      //--- Create the event:
      //--- Get the base and main objects
      CWinFormBase *base=this.GetBase();
      CWinFormBase *main=this.GetMain();
      //--- in the 'long' event parameter, pass a string, while in the 'double' parameter, the tab header location column
      long lp=this.Row();
      double dp=this.Column();
      //--- in the 'string' parameter of the event, pass the names of the main and base objects separated by ";"
      string name_main=(main!=NULL ? main.Name() : "");
      string name_base=(base!=NULL ? base.Name() : "");
      string sp=name_main+";"+name_base;
      //--- Send the tab selection event to the chart of the control program
      ::EventChartCustom(::ChartID(),WF_CONTROL_EVENT_TAB_SELECT,lp,dp,sp);
      //--- Set the frame color for "The cursor is over the active area" status
      this.SetBorderColor(this.BorderColorMouseOver(),false);
     }
  }
//+------------------------------------------------------------------+

Hier erstellen wir ein Ereignis, das an den Event-Handler der Bibliothek gesendet wird, und in dieser Nachricht geben wir die Nummer der Kopfzeile im Parameter „long lp“ der Nachricht an (bei einer Zeile ist es immer Null), der Index der Kopfspalte in der Zeile wird im Parameter „double dp“ angegeben, der definitiv angibt, welcher Titel angeklickt wurde (der Index der ausgewählten Registerkarte). Um das TabControl, in dem die Registerkarte ausgewählt wurde, eindeutig zu identifizieren, müssen wir in einem Ereignis den Namen des Hauptobjekts, in dem das Ereignis aufgetreten ist, und den Namen des TabControls, in dem die Registerkarte ausgewählt wurde, senden. Da wir nur einen String-Parameter haben, fügen wir einfach die Namen des Hauptobjekts und des TabControl-Objekts mit „;“ als Trennzeichen hinzu. In er Ereignisbehandlung können wir dann die Zeichenkette durch das Trennzeichen teilen, um beide Namen dieser Objekte erhalten.


Lassen Sie uns nun die Klasse des WinForms-Objekts TabControl in \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqh verfeinern.

Wir korrigieren die Pfade zu den Hilfsobjektdateien, deklarieren die neuen Variablen und Methoden und implementieren die Methoden, die die Zeiger auf die Pfeiltastenobjekte zurückgeben:

//+------------------------------------------------------------------+
//|                                                   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 "Container.mqh"
#include "GroupBox.mqh"
#include "..\Helpers\TabHeader.mqh"
#include "..\Helpers\TabField.mqh"
//+------------------------------------------------------------------+
//| TabHeader object class of WForms TabControl                      |
//+------------------------------------------------------------------+
class CTabControl : public CContainer
  {
private:
   int               m_item_width;                    // Fixed width of tab titles
   int               m_item_height;                   // Fixed height of tab titles
   int               m_header_padding_x;              // Additional header width if DrawMode==Fixed
   int               m_header_padding_y;              // Additional header height if DrawMode==Fixed
   int               m_field_padding_top;             // Padding of top tab fields
   int               m_field_padding_bottom;          // Padding of bottom tab fields
   int               m_field_padding_left;            // Padding of left tab fields
   int               m_field_padding_right;           // Padding of right tab fields
   bool              m_arr_butt_ud_visible_flag;      // Tab header "up-down" control buttons visibility flag 
   bool              m_arr_butt_lr_visible_flag;      // Tab header "left-right" control buttons visibility flag
//--- (1) Hide and (2) display right-left and up-down button controls
   void              ShowArrLeftRightBox(void);
   void              ShowArrUpDownBox(void);
   void              HideArrLeftRightBox(void);
   void              HideArrUpDownBox(void);
//--- Move right-left and up-down button controls to the foreground
   void              BringToTopArrLeftRightBox(void);
   void              BringToTopArrUpDownBox(void);
//--- Create a new graphical object
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);

//--- Return the list of (1) headers, (2) tab fields, the pointer to the (3) up-down and (4) left-right button objects
   CArrayObj        *GetListHeaders(void)          { return this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);        }
   CArrayObj        *GetListFields(void)           { return this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD);         }
   CArrowUpDownBox  *GetArrUpDownBox(void)         { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,0); }
   CArrowLeftRightBox *GetArrLeftRightBox(void)    { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,0); }
//--- Set the tab as selected


Im öffentlichen Abschnitt der Klasse implementieren wir die Methoden für die Behandlung neuer Variablen und Pfeilschaltflächenobjekte und deklarieren die Methode zum Verschieben der Kopfzeile sowie die Ereignisbehandlung:

//--- Return a pointer to the (1) tab header, (2) field, (3) the number of tabs, visibility of (4) left-right and (5) up-down buttons
   CTabHeader       *GetTabHeader(const int index)       { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index);    }
   CWinFormBase     *GetTabField(const int index)        { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,index);     }
   int               TabPages(void)                      { return(this.GetListHeaders()!=NULL ? this.GetListHeaders().Total() : 0); }
   bool              IsVisibleLeftRightBox(void)         { return this.m_arr_butt_lr_visible_flag;                                  }
   bool              IsVisibleUpDownBox(void)            { return this.m_arr_butt_ud_visible_flag;                                  }
//--- Set the visibility of the (1) left-right, (2) up-down buttons
   void              SetVisibleLeftRightBox(const bool flag);
   void              SetVisibleUpDownBox(const bool flag);
//--- Set the size of the (1) left-right, (2) up-down buttons
   void              SetSizeLeftRightBox(const int value);
   void              SetSizeUpDownBox(const int value);
//--- (1) Set and (2) return the location of tabs on the control

...

//--- Set the object above all
   virtual void      BringToTop(void);
//--- Show the control
   virtual void      Show(void);
//--- Shift the header row
   void              ShiftHeadersRow(const int selected);
//--- Event handler
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- Constructor
                     CTabControl(const long chart_id,
                                 const int subwindow,
                                 const string descript,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h);
  };
//+------------------------------------------------------------------+


Im Klassenkonstruktor legen wir die Standard-Sichtbarkeitswerte für Pfeilobjekte fest:

//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CTabControl::CTabControl(const long chart_id,
                         const int subwindow,
                         const string descript,
                         const int x,
                         const int y,
                         const int w,
                         const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   this.SetBorderSizeAll(0);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetOpacity(0,true);
   this.SetBackgroundColor(CLR_CANV_NULL,true);
   this.SetBackgroundColorMouseDown(CLR_CANV_NULL);
   this.SetBackgroundColorMouseOver(CLR_CANV_NULL);
   this.SetBorderColor(CLR_CANV_NULL,true);
   this.SetBorderColorMouseDown(CLR_CANV_NULL);
   this.SetBorderColorMouseOver(CLR_CANV_NULL);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP);
   this.SetItemSize(58,18);
   this.SetTabSizeMode(CANV_ELEMENT_TAB_SIZE_MODE_NORMAL);
   this.SetPaddingAll(0);
   this.SetHeaderPadding(6,3);
   this.SetFieldPadding(3,3,3,3);
   this.m_arr_butt_ud_visible_flag=false;
   this.m_arr_butt_lr_visible_flag=false;
  }
//+------------------------------------------------------------------+

Beide Objekte sollten standardmäßig ausgeblendet sein und werden nur angezeigt, wenn entweder die horizontale oder die vertikale Kopfleiste über den Container hinausragt. Mit denselben Flags werden wir feststellen, ob es notwendig ist, dem Sichtbarkeitsbereich in den Registerkartenkopfobjekten zusätzliche Werte hinzuzufügen. Wenn es kein Pfeilobjekt gibt, werden die Kopfzeilen entlang der Kante des Containers abgeschnitten. Wenn das Pfeilobjekt vorhanden ist, werden sie entlang der Kante des Objekts beschnitten, damit sich das Schaltflächenobjekt mit Pfeilen nicht mit der entlang der Kante des Containers beschnittenen Kopfzeile überschneidet.


In der Methode, die die angegebene Anzahl von Registerkarten erstellt, passen wir die Y-Koordinate für die Platzierung der Kopfzeilen und die Höhe der Registerkartenfelder an, wenn sie am unteren Rand des Containers platziert werden, und fügen den Codeblock für die Erstellung von zwei Schaltflächenobjekten „links-rechts“ und „oben-unten“ hinzu:

//+------------------------------------------------------------------+
//| Create the specified number of tabs                              |
//+------------------------------------------------------------------+
bool CTabControl::CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="")
  {
//--- Calculate the size and initial coordinates of the tab title
   int w=(tab_w==0 ? this.ItemWidth()  : tab_w);
   int h=(tab_h==0 ? this.ItemHeight() : tab_h);

//--- In the loop by the number of tabs
   CTabHeader *header=NULL;
   CTabField  *field=NULL;
   for(int i=0;i<total;i++)
     {
      //--- Depending on the location of tab titles, set their initial coordinates
      int header_x=2;
      int header_y=2;
      int header_w=w;
      int header_h=h;
      
      //--- Set the current X and Y coordinate depending on the location of the tab headers
      switch(this.Alignment())
        {
         case CANV_ELEMENT_ALIGNMENT_TOP     :
           header_w=w;
           header_h=h;
           header_x=(header==NULL ? 2 : header.RightEdgeRelative());
           header_y=2;
           break;
         case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
           header_w=w;
           header_h=h;
           header_x=(header==NULL ? 2 : header.RightEdgeRelative());
           header_y=this.Height()-header_h-2;
           break;
         case CANV_ELEMENT_ALIGNMENT_LEFT    :
           header_w=h;
           header_h=w;
           header_x=2;
           header_y=(header==NULL ? this.Height()-header_h-2 : header.CoordYRelative()-header_h);
           break;
         case CANV_ELEMENT_ALIGNMENT_RIGHT   :
           header_w=h;
           header_h=w;
           header_x=this.Width()-header_w-2;
           header_y=(header==NULL ? 2 : header.BottomEdgeRelative());
           break;
         default:
           break;
        }
      //--- Create the TabHeader object
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,header_x,header_y,header_w,header_h,clrNONE,255,this.Active(),false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1));
         return false;
        }
      header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,i);
      if(header==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1));
         return false;
        }
      header.SetBase(this.GetObject());
      header.SetPageNumber(i);
      header.SetGroup(this.Group()+1);
      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.SetBorderStyle(FRAME_STYLE_SIMPLE);
      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.SetAlignment(this.Alignment());
      header.SetPadding(this.HeaderPaddingWidth(),this.HeaderPaddingHeight(),this.HeaderPaddingWidth(),this.HeaderPaddingHeight());
      if(header_text!="" && header_text!=NULL)
         this.SetHeaderText(header,header_text+string(i+1));
      else
         this.SetHeaderText(header,"TabPage"+string(i+1));
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT)
         header.SetFontAngle(90);
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT)
         header.SetFontAngle(270);
      header.SetTabSizeMode(this.TabSizeMode());
      
      //--- Save the initial height of the header and set its size in accordance with the header size setting mode
      int h_prev=header_h;
      header.SetSizes(header_w,header_h);
      //--- Get the Y offset of the header position after changing its height and
      //--- shift it by the calculated value only for headers on the left
      int y_shift=header.Height()-h_prev;
      if(header.Move(header.CoordX(),header.CoordY()-(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT ? y_shift : 0)))
        {
         header.SetCoordXRelative(header.CoordX()-this.CoordX());
         header.SetCoordYRelative(header.CoordY()-this.CoordY());
        }
      header.SetVisibleFlag(this.IsVisible(),false);

      //--- Depending on the location of the tab headers, set the initial coordinates of the tab fields
      int field_x=0;
      int field_y=0;
      int field_w=this.Width();
      int field_h=this.Height()-header.Height()-2;
      int header_shift=0;
      
      switch(this.Alignment())
        {
         case CANV_ELEMENT_ALIGNMENT_TOP     :
           field_x=0;
           field_y=header.BottomEdgeRelative();
           field_w=this.Width();
           field_h=this.Height()-header.Height()-2;
           break;
         case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
           field_x=0;
           field_y=0;
           field_w=this.Width();
           field_h=this.Height()-header.Height()-2;
           break;
         case CANV_ELEMENT_ALIGNMENT_LEFT    :
           field_x=header.RightEdgeRelative();
           field_y=0;
           field_h=this.Height();
           field_w=this.Width()-header.Width()-2;
           break;
         case CANV_ELEMENT_ALIGNMENT_RIGHT   :
           field_x=0;
           field_y=0;
           field_h=this.Height();
           field_w=this.Width()-header.Width()-2;
           break;
         default:
           break;
        }
      
      //--- Create the TabField object (tab field)
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,field_x,field_y,field_w,field_h,clrNONE,255,true,false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1));
         return false;
        }
      field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,i);
      if(field==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1));
         return false;
        }
      field.SetBase(this.GetObject());
      field.SetPageNumber(i);
      field.SetGroup(this.Group()+1);
      field.SetBorderSizeAll(1);
      field.SetBorderStyle(FRAME_STYLE_SIMPLE);
      field.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true);
      field.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true);
      field.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
      field.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
      field.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
      field.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
      field.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
      field.SetForeColor(CLR_DEF_FORE_COLOR,true);
      field.SetPadding(this.FieldPaddingLeft(),this.FieldPaddingTop(),this.FieldPaddingRight(),this.FieldPaddingBottom());
      field.Hide();
     }
//--- Create left-right and up-down buttons
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,this.Width()-32,0,15,15,clrNONE,255,this.Active(),false);
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,0,this.Height()-32,15,15,clrNONE,255,this.Active(),false);
   CArrowUpDownBox *box_ud=this.GetArrUpDownBox();
   if(box_ud!=NULL)
     {
      this.SetVisibleUpDownBox(false);
      this.SetSizeUpDownBox(box_ud.Height());
      box_ud.SetBorderStyle(FRAME_STYLE_NONE);
      box_ud.SetBackgroundColor(CLR_CANV_NULL,true);
      box_ud.SetOpacity(0);
      box_ud.Hide();
     }
   CArrowLeftRightBox *box_lr=this.GetArrLeftRightBox();
   if(box_lr!=NULL)
     {
      this.SetVisibleLeftRightBox(false);
      this.SetSizeLeftRightBox(box_lr.Width());
      box_lr.SetBorderStyle(FRAME_STYLE_NONE);
      box_lr.SetBackgroundColor(CLR_CANV_NULL,true);
      box_lr.SetOpacity(0);
      box_lr.Hide();
     }
//--- Arrange all titles in accordance with the specified display modes and select the specified tab
   this.ArrangeTabHeaders();
   this.Select(selected_page,true);
   return true;
  }
//+------------------------------------------------------------------+

Wenn Kopfzeilen am unteren Rand platziert werden, sollte ihre Y-Koordinate zwei Pixel höher sein als der untere Rand des Containers, da der Titel der ausgewählten Registerkarte um zwei Pixel größer wird und, wenn er am unteren Rand des Containers platziert wird, seine untere Kante außerhalb des Containers liegt und dort abgeschnitten wird, wenn er ausgewählt wird und dementsprechend seine Höhe um zwei Pixel erhöht wird.

Um ein Beschneiden zu verhindern, sollte das Objekt zwei Pixel höher platziert werden, wobei zu berücksichtigen ist, dass es bei der Auswahl möglicherweise größer wird. Dementsprechend sollte das Tabulatorfeld zwei Pixel kleiner sein, da diese zwei Pixel nun durch die Verschiebung der Kopfzeilenkoordinate nach oben „aufgefressen“ werden.

Nachdem wir zwei Schaltflächenobjekte mit Pfeilen erstellt haben, versetzen wir diese in den Zustand „hidden“ (ausgeblendet, in diesem Fall werden diese Zustände auf die Registerkarten-Kopfobjekte übertragen) und setzen die Größen der erstellten Objekte auf alle Registerkarten-Kopfobjekte. All dies geschieht mit zwei Methoden, auf die ich später noch eingehen werde. Für Objekte wird der Rahmentyp auf „none“ (nicht) gesetzt. Außerdem legen wir die transparente Hintergrundfarbe und die volle Deckkraft fest. Am Ende ist das Objekt versteckt.

Daher werden diese beiden Objekte als zwei Schaltflächen auf einem transparenten Hintergrund erstellt, um dem Aussehen der entsprechenden Objekte im MS Visual Studio-Steuerelement zu entsprechen.

In allen Methoden, die Tabulator-Kopfzeilen oben, unten, links und rechts platzieren, fügen wir eine Zeilen- und Spaltenindex-Einstellung für die Kopfzeile sowie den Codeblock hinzu, um die Situation zu handhaben, wenn wir Tabulator-Kopfzeilen in einem String aktiviert haben, sowie für den Fall, dass die Tabulator-Kopfzeile außerhalb des Containers liegt:

//+------------------------------------------------------------------+
//| Arrange tab headers on top                                       |
//+------------------------------------------------------------------+
void CTabControl::ArrangeTabHeadersTop(void)
  {
//--- Get the list of tab headers
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
//--- Declare the variables
   int col=0;                                // Column
   int row=0;                                // Row
   int x1_base=2;                            // Initial X coordinate
   int x2_base=this.Width()-2;               // Final X coordinate
   int x_shift=0;                            // Shift the tab set for calculating their exit beyond the container
   int n=0;                                  // The variable for calculating the column index relative to the loop index
//--- In a loop by the list of headers,
   for(int i=0;i<list.Total();i++)
     {
      //--- get the next tab header object
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      //--- If the flag for positioning headers in several rows is set
      if(this.Multiline())
        {
         //--- Calculate the value of the right edge of the header, taking into account that
         //--- the origin always comes from the left edge of TabControl + 2 pixels
         int x2=header.RightEdgeRelative()-x_shift;
         //--- If the calculated value does not go beyond the right edge of the TabControl minus 2 pixels, 
         //--- set the column number equal to the loop index minus the value in the n variable
         if(x2<x2_base)
            col=i-n;
         //--- If the calculated value goes beyond the right edge of the TabControl minus 2 pixels,
         else
           {
            //--- Increase the row index, calculate the new shift (so that the next object is compared with the TabControl left edge + 2 pixels),
            //--- set the loop index for the n variable, while the column index is set to zero, this is the start of the new row
            row++;
            x_shift=header.CoordXRelative()-2;
            n=i;
            col=0;
           }
         //--- Assign the row and column indices to the tab header and shift it to the calculated coordinates
         header.SetTabLocation(row,col);
         if(header.Move(header.CoordX()-x_shift,header.CoordY()-header.Row()*header.Height()))
           {
            header.SetCoordXRelative(header.CoordX()-this.CoordX());
            header.SetCoordYRelative(header.CoordY()-this.CoordY());
           }
        }
      //--- If only one row of headers is allowed
      else
        {
         header.SetRow(0);
         header.SetColumn(i);
        }
     }

//--- The location of all tab titles is set. Now place them all together with the fields
//--- according to the header row and column indices.

//--- Get the last title in the list
   CTabHeader *last=this.GetTabHeader(list.Total()-1);
//--- If the object is received
   if(last!=NULL)
     {
      //--- If the mode of stretching headers to the width of the container is set, call the stretching method
      if(this.TabSizeMode()==CANV_ELEMENT_TAB_SIZE_MODE_FILL)
         this.StretchHeaders();
      //--- If this is not the first row (with index 0)
      if(last.Row()>0)
        {
         //--- Calculate the offset of the tab field Y coordinate
         int y_shift=last.Row()*last.Height();
         //--- In a loop by the list of headers,
         for(int i=0;i<list.Total();i++)
           {
            //--- get the next object
            CTabHeader *header=list.At(i);
            if(header==NULL)
               continue;
            //--- get the tab field corresponding to the received header
            CTabField  *field=header.GetFieldObj();
            if(field==NULL)
               continue;
            //--- shift the tab header by the calculated row coordinates
            if(header.Move(header.CoordX(),header.CoordY()+y_shift))
              {
               header.SetCoordXRelative(header.CoordX()-this.CoordX());
               header.SetCoordYRelative(header.CoordY()-this.CoordY());
              }
            //--- shift the tab field by the calculated shift
            if(field.Move(field.CoordX(),field.CoordY()+y_shift))
              {
               field.SetCoordXRelative(field.CoordX()-this.CoordX());
               field.SetCoordYRelative(field.CoordY()-this.CoordY());
               //--- change the size of the shifted field by the value of its shift
               field.Resize(field.Width(),field.Height()-y_shift,false);
              }
           }
        }
      //--- If this is the first and only string
      else if(!this.Multiline())
        {
         //--- If the right edge of the header goes beyond the right edge of the container area,
         if(last.RightEdge()>this.RightEdgeWorkspace())
           {
            //--- get the button object with left-right arrows
            CArrowLeftRightBox *arr_box=this.GetArrLeftRightBox();
            if(arr_box!=NULL)
              {
               //--- Calculate object location coordinates
               int x=this.RightEdgeWorkspace()-arr_box.Width()+1;
               int y=last.BottomEdge()-arr_box.Height();
               //--- If the object is shifted by the specified coordinates,
               if(arr_box.Move(x,y))
                 {
                  //--- set its relative coordinates
                  arr_box.SetCoordXRelative(arr_box.CoordX()-this.CoordX());
                  arr_box.SetCoordYRelative(arr_box.CoordY()-this.CoordY());
                  //--- set the visibility flag for the object
                  this.SetVisibleLeftRightBox(true);
                  //--- If the control is visible, display the buttons and bring them to the foreground
                  if(this.IsVisible())
                    {
                     arr_box.Show();
                     arr_box.BringToTop();
                    }
                 }
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

Hier ist die vollständige Methode für die Platzierung von Registerkartenüberschriften im oberen Bereich. Die anderen drei Methoden weisen dieselbe Einstellung des Zeilen- (Null) und Spaltenindex (Schleifenindex) für die Registerkartenüberschrift auf, während die Codeblöcke, die die über den Container hinausgehende Überschrift definieren und die Bildlaufsteuerungsschaltflächen für die Zeile anzeigen, bei der Berechnung der Position der Überschriften im Verhältnis zum Container und der Anzeige der Bildlaufsteuerungsschaltflächen an den entsprechenden Koordinaten leicht unterschiedlich sind.

Schauen wir uns diese Codeblöcke für verschiedene Methoden an.

Für die Methode, die Registerkartenüberschriften am unteren Rand platziert (ArrangeTabHeadersBottom()):

      //--- If this is the first and only string
      else if(!this.Multiline())
        {
         if(last.RightEdge()>this.RightEdgeWorkspace())
           {
            CArrowLeftRightBox *arr_box=this.GetArrLeftRightBox();
            if(arr_box!=NULL)
              {
               int x=this.RightEdgeWorkspace()-arr_box.Width()+1;
               int y=last.CoordY();
               if(arr_box.Move(x,y))
                 {
                  arr_box.SetCoordXRelative(arr_box.CoordX()-this.CoordX());
                  arr_box.SetCoordYRelative(arr_box.CoordY()-this.CoordY());
                  this.SetVisibleLeftRightBox(true);
                  if(this.IsVisible())
                    {
                     arr_box.Show();
                     arr_box.BringToTop();
                    }
                 }
              }
           }
        }


Für die Methode, die die Registerkartenüberschriften links anordnet (ArrangeTabHeadersLeft()):

      //--- If this is the first and only string
      else if(!this.Multiline())
        {
         if(last.CoordY()<this.CoordY())
           {
            CArrowUpDownBox *arr_box=this.GetArrUpDownBox();
            if(arr_box!=NULL)
              {
               int x=last.RightEdge()-arr_box.Width();
               int y=this.CoordY()-1;
               if(arr_box.Move(x,y))
                 {
                  arr_box.SetCoordXRelative(arr_box.CoordX()-this.CoordX());
                  arr_box.SetCoordYRelative(arr_box.CoordY()-this.CoordY());
                  this.SetVisibleUpDownBox(true);
                  if(this.IsVisible())
                    {
                     arr_box.Show();
                     arr_box.BringToTop();
                    }
                 }
              }
           }
        }


Für die Methode, die Registerkartenüberschriften nach rechts positioniert (ArrangeTabHeadersRight()):

      //--- If this is the first and only string
      else if(!this.Multiline())
        {
         if(last.BottomEdge()>this.BottomEdge())
           {
            CArrowUpDownBox *arr_box=this.GetArrUpDownBox();
            if(arr_box!=NULL)
              {
               int x=last.CoordX();
               int y=this.BottomEdgeWorkspace()-arr_box.Height()+1;
               if(arr_box.Move(x,y))
                 {
                  arr_box.SetCoordXRelative(arr_box.CoordX()-this.CoordX());
                  arr_box.SetCoordYRelative(arr_box.CoordY()-this.CoordY());
                  this.SetVisibleUpDownBox(true);
                  if(this.IsVisible())
                    {
                     arr_box.Show();
                     arr_box.BringToTop();
                    }
                 }
              }
           }
        }

Wenn man alle vier Codeblöcke in allen vier Methoden vergleicht, sieht man, wie die Koordinaten der Schaltflächen der Bildlaufsteuerung berechnet und die Kopfzeilen, die über den Container hinausragen, definiert werden.

Bei der Methode zur Erstellung eines neuen grafischen Objekts wurden auch Änderungen bei der Formatierung von „switch“-Anweisungen vorgenommen:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CTabControl::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                         const int obj_num,
                                         const string descript,
                                         const int x,
                                         const int y,
                                         const int w,
                                         const int h,
                                         const color colour,
                                         const uchar opacity,
                                         const bool movable,
                                         const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT                 : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break;
      case GRAPH_ELEMENT_TYPE_FORM                    : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h);              break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL                : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL                : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX             : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON          : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON               : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX             : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM        : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX     : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX      : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER           : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD            : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL          : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);  break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      default  : break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+

Jetzt steht alles in einer Zeile — für eine kompaktere Darstellung der Methodenlogik.

Wir ersetzen in der virtuellen Methode, die das Objekt vor allem setzt, den Aufruf der Methode BringToTop() des Formularobjekts

//+------------------------------------------------------------------+
//| Set the object above all the rest                                |
//+------------------------------------------------------------------+
void CTabControl::BringToTop(void)
  {
//--- Move all elements of the object to the foreground
   CForm::BringToTop();
//--- Get the index of the selected tab

mit dem Aufruf der Methode Show() des grafischen Elementobjekts. Wenn das Objekt selbst sichtbar ist, zeigen wir die Steuerobjekte für das Scrollen der Kopfleisten an, falls diese Objekte sichtbar sein sollten:

//+------------------------------------------------------------------+
//| Set the object above all the rest                                |
//+------------------------------------------------------------------+
void CTabControl::BringToTop(void)
  {
//--- Move all elements of the object to the foreground
   CGCnvElement::Show();
//--- Get the index of the selected tab
   int selected=this.SelectedTabPageNum();
//--- Declare the pointers to tab header objects and tab fields
   CTabHeader *header=NULL;
   CTabField  *field=NULL;
//--- Get the list of all tab headers
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
//--- In a loop by the list of tab headers,
   for(int i=0;i<list.Total();i++)
     {
      //--- get the next header, and if failed to get the object,
      //--- or this is the header of the selected tab, skip it
      header=list.At(i);
      if(header==NULL || header.PageNumber()==selected)
         continue;
      //--- bring the header to the foreground
      header.BringToTop();
      //--- get the tab field corresponding to the current header
      field=header.GetFieldObj();
      if(field==NULL)
         continue;
      //--- Hide the tab field
      field.Hide();
     }
//--- Get the pointer to the title of the selected tab
   header=this.GetTabHeader(selected);
   if(header!=NULL)
     {
      //--- bring the header to the front
      header.BringToTop();
      //--- get the tab field corresponding to the selected tab header
      field=header.GetFieldObj();
      //--- Display the tab field on the foreground
      if(field!=NULL)
         field.BringToTop();
     }
//--- If the object is visible and the "up-down" and "left-right" buttons should be visible, move them to the foreground
   if(this.IsVisible())
     {
      if(this.m_arr_butt_ud_visible_flag)
         this.BringToTopArrUpDownBox();
      if(this.m_arr_butt_lr_visible_flag)
         this.BringToTopArrLeftRightBox();
     }
  }
//+------------------------------------------------------------------+

Als ich die BringToTop-Methode der CForm-Elternklasse aufrief, wurden alle an das Steuerelement gebundenen Objekte in den Vordergrund geholt und damit sichtbar gemacht. Jetzt müssen wir die Sichtbarkeit der Scroll Control Button Objekte kontrollieren. Daher machen wir einfach das Objekt selbst sichtbar und prüfen dann innerhalb der Methode, ob die Überschriften und Felder der Registerkarten sowie die Schaltflächen, die das Blättern der Kopfzeilen steuern, angezeigt werden müssen.

Die Methode, die die „Oben-Unten-Tasten“ anzeigt:

//+------------------------------------------------------------------+
//| Display Up-down button controls                                  |
//+------------------------------------------------------------------+
void CTabControl::ShowArrUpDownBox(void)
  {
   CArrowUpDownBox *box=this.GetArrUpDownBox();
   if(box==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ)," ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX));
      return;
     }
   box.Show();
  }
//+------------------------------------------------------------------+

Abrufen des Zeigers auf das Steuerelement. Wenn der Zeiger nicht gefunden wird, wird eine Fehlermeldung angezeigt und die Methode beendet. Nach erfolgreichem Empfang des Zeigers wird das Objekt angezeigt.


Die Methode zur Anzeige der „Rechts-Links-Taste“:

//+------------------------------------------------------------------+
//| Display the Right-left button controls                           |
//+------------------------------------------------------------------+
void CTabControl::ShowArrLeftRightBox(void)
  {
   CArrowLeftRightBox *box=this.GetArrLeftRightBox();
   if(box==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ)," ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX));
      return;
     }
   box.Show();
  }
//+------------------------------------------------------------------+

Die Logik der Methode ist identisch mit der oben beschriebenen.


Die Methoden zum Ausblenden der Steuerelemente „Oben-Unten-Tasten“ und „Rechts-Links-Tasten“:

//+------------------------------------------------------------------+
//| Hide the Up-down button controls                                 |
//+------------------------------------------------------------------+
void CTabControl::HideArrUpDownBox(void)
  {
   CArrowUpDownBox *box=this.GetArrUpDownBox();
   if(box==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ)," ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX));
      return;
     }
   box.Hide();
  }
//+------------------------------------------------------------------+
//| Hide the Right-left button controls                              |
//+------------------------------------------------------------------+
void CTabControl::HideArrLeftRightBox(void)
  {
   CArrowLeftRightBox *box=this.GetArrLeftRightBox();
   if(box==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ)," ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX));
      return;
     }
   box.Hide();
  }
//+------------------------------------------------------------------+

Die Logik der Methoden ist identisch mit der Logik der beiden oben beschriebenen Methoden zur Anzeige von Steuerelementen. Aber hier wird das Element nicht angezeigt, sondern ausgeblendet.


Die Methoden, die die Steuerelemente „Oben-Unten-Tasten“ und „Rechts-Links-Tasten“ in den Vordergrund bringen:

//+------------------------------------------------------------------+
//| Move Up-down button controls to the foreground                   |
//+------------------------------------------------------------------+
void CTabControl::BringToTopArrUpDownBox(void)
  {
   CArrowUpDownBox *box=this.GetArrUpDownBox();
   if(box==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ)," ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX));
      return;
     }
   box.BringToTop();
  }
//+------------------------------------------------------------------+
//|Move right-left button controls to the foreground                 |
//+------------------------------------------------------------------+
void CTabControl::BringToTopArrLeftRightBox(void)
  {
   CArrowLeftRightBox *box=this.GetArrLeftRightBox();
   if(box==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ)," ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX));
      return;
     }
   box.BringToTop();
  }
//+------------------------------------------------------------------+

Alles ist genau gleich, außer dass man sich in den Vordergrund bewegt.


Die Methode, mit der die Sichtbarkeit der Links-Rechts-Schaltflächen eingestellt wird:

//+------------------------------------------------------------------+
//| Set the visibility of the left-right buttons                     |
//+------------------------------------------------------------------+
void CTabControl::SetVisibleLeftRightBox(const bool flag)
  {
   this.m_arr_butt_lr_visible_flag=flag;
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
   for(int i=0;i<list.Total();i++)
     {
      CTabHeader *obj=list.At(i);
      if(obj==NULL)
         continue;
      obj.SetVisibleLeftRightBox(flag);
     }
  }
//+------------------------------------------------------------------+

Zunächst setzen wir das Sichtbarkeitsflag des Steuerelements, holen dann die Liste der Registerkartenüberschriften und setzen in der Schleife durch die Liste für jedes der Überschriftenobjekte den an die Methode übergebenen Flag-Wert.


Die Methode zur Einstellung der Sichtbarkeit der Oben-Unten-Tasten:

//+------------------------------------------------------------------+
//| Set the visibility of up-down buttons                            |
//+------------------------------------------------------------------+
void CTabControl::SetVisibleUpDownBox(const bool flag)
  {
   this.m_arr_butt_ud_visible_flag=flag;
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
   for(int i=0;i<list.Total();i++)
     {
      CTabHeader *obj=list.At(i);
      if(obj==NULL)
         continue;
      obj.SetVisibleUpDownBox(flag);
     }
  }
//+------------------------------------------------------------------+

Die Logik der Methode ist identisch mit der oben beschriebenen. Hier werden die Flags für Oben-Unten-Tasten-Objekte gesetzt.


Die Methode zur Einstellung der Größe der Links/Rechts-Tasten:

//+------------------------------------------------------------------+
//| Set the size of left-right buttons                               |
//+------------------------------------------------------------------+
void CTabControl::SetSizeLeftRightBox(const int value)
  {
   CArrowLeftRightBox *butt=this.GetArrLeftRightBox();
   if(butt!=NULL)
      butt.Resize(value,butt.Height(),false);
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
   for(int i=0;i<list.Total();i++)
     {
      CTabHeader *obj=list.At(i);
      if(obj==NULL)
         continue;
      obj.SetSizeLeftRightBox(value);
     }
  }
//+------------------------------------------------------------------+

Wir holen uns den Zeiger auf das Links-Rechts-Schaltflächenobjekt, setzen den an die Methode übergebenen Wert als Objektbreite (da wir hier nur die Objektbreite ändern müssen). Als Nächstes setzen wir in der Schleife durch die Liste der Registerkartenüberschriften den Wert einer bestimmten Breite der Blättertastenobjekte auf jedes nachfolgende Objekt.


Die Methode, mit der die Größe der Oben-Unten-Tasten festgelegt wird:

//+------------------------------------------------------------------+
//| Set the up-down button size                                      |
//+------------------------------------------------------------------+
void CTabControl::SetSizeUpDownBox(const int value)
  {
   CArrowUpDownBox *butt=this.GetArrUpDownBox();
   if(butt!=NULL)
      butt.Resize(butt.Width(),value,false);
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
   for(int i=0;i<list.Total();i++)
     {
      CTabHeader *obj=list.At(i);
      if(obj==NULL)
         continue;
      obj.SetSizeUpDownBox(value);
     }
  }
//+------------------------------------------------------------------+

Die Logik der Methode ist identisch mit der oben beschriebenen. Aber hier holen wir uns den Zeiger auf das Oben-Unten-Tasten-Objekt und setzen die Objekthöhe.


Die Methode, die die Kopfzeile verschiebt:

//+------------------------------------------------------------------+
//| Shift the header row                                             |
//+------------------------------------------------------------------+
void CTabControl::ShiftHeadersRow(const int selected)
  {
//--- If there are multiline headers, leave
   if(this.Multiline())
      return;
//--- Get the list of tab headers
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
//--- Get the header of the selected tab
   CTabHeader *header=this.GetTabHeader(selected);
   if(header==NULL)
      return;
//--- Check how much of the selected header is cropped on the right
   int hidden=header.RightEdge()-this.RightEdgeWorkspace();
//--- If the header is not cropped, exit
   if(hidden<0)
      return;
   CTabHeader *obj=NULL;
   int shift=0;
//--- Look for the leftmost one starting from the selected header
   for(int i=selected-1;i>WRONG_VALUE;i--)
     {
      obj=list.At(i);
      if(obj==NULL)
         continue;
      //--- If the leftmost one is found, remember how much to shift all headers to the right
      if(obj.CoordX()-2==this.CoordX())
         shift=obj.Width();
     }
//--- In a loop by the list of headers,
   for(int i=0;i<list.Total();i++)
     {
      //--- get the next header
      obj=list.At(i);
      if(obj==NULL)
         continue;
      //--- and, if the header is shifted to the left by 'shift',
      if(obj.Move(obj.CoordX()-shift,obj.CoordY()))
        {
         //--- save its new relative coordinates
         obj.SetCoordXRelative(obj.CoordX()-this.CoordX());
         obj.SetCoordYRelative(obj.CoordY()-this.CoordY());
         //--- If the title has gone beyond the left edge,
         if(obj.CoordX()-2<this.CoordX())
           {
            //--- crop and hide it
            obj.Crop();
            obj.Hide();
           }
         //--- If the header fits the visible area of the control,
         else
           {
            //--- display and redraw it
            obj.Show();
            obj.Redraw(false);
            //--- Get the tab field corresponding to the header
            CTabField *field=obj.GetFieldObj();
            if(field==NULL)
               continue;
            //--- If this is a selected header,
            if(i==selected)
              {
               //--- Draw the field frame
               field.DrawFrame();
               field.Update();
              }
           }
        }
     }
//--- Redraw the chart to display changes immediately
   ::ChartRedraw(this.ChartID());
  }
//+------------------------------------------------------------------+

Hier wird die Logik der Methode in den Kommentaren zum Code detailliert beschrieben. Kurz gesagt, der Index der angeklickten Überschrift (Auswahl der Registerkarte des TabControl-Objekts) wird an die Methode übergeben. Wenn die ausgewählte Registerkarte, d. h. ihre Kopfzeile, außerhalb des Containers liegt, d. h. abgeschnitten ist, müssen wir alle Kopfzeilen nach links verschieben, damit die ausgewählte Kopfzeile vollständig sichtbar wird. Die allererste links sichtbare Kopfzeile geht über den linken Rand hinaus, und die nächste nimmt ihren Platz ein.

Daher müssen wir zunächst die erste sichtbare Kopfzeile auf der linken Seite finden und ihre Breite ermitteln (jede Kopfzeile kann ihre eigene Breite haben, wenn die Größen entsprechend dem Kopfzeilentext eingestellt sind). Die Breite der gefundenen ersten Kopfzeile gibt den Betrag an, um den die gesamte Kopfzeile nach links verschoben werden soll. Nach dem Verschieben der Zeile suchen wir das Feld für die ausgewählte Kopfzeile und zeichnen seinen Rahmen neu, da der Feldrahmen relativ zur Position der Kopfzeile gezeichnet wird. Da er nun verschoben wurde, wird der Rahmen nicht mehr korrekt gezeichnet.

Diese Methode ist nur geeignet, um eine Reihe von horizontalen Überschriften nach links zu verschieben. Auf der Grundlage dieser Methode werde ich im nächsten Artikel die Methoden zum Verschieben der Kopfleiste in alle Richtungen - links-rechts und oben-unten - erstellen.


Die Ereignisbehandlung:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CTabControl::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Adjust subwindow Y shift
   CGCnvElement::OnChartEvent(id,lparam,dparam,sparam);
   if(id==WF_CONTROL_EVENT_TAB_SELECT)
     {
      this.ShiftHeadersRow((int)dparam);
     }
  }
//+------------------------------------------------------------------+

Zunächst rufen wir die Methode zur Anpassung des Y-Offsets für das Unterfenster der grafischen Objektklasse auf (dies gilt für jedes grafische Element auf der Leinwand, aber hier wird die Ereignisbehandlung der Elternklasse überschrieben, sodass wir nicht vergessen sollten, die Methode zur Anpassung der Koordinaten aufzurufen). Dann rufen wir die oben beschriebene Methode zum Versetzen der Registerkartenkopfleiste auf.


Jetzt müssen wir die Kollektionsklasse der grafischen Elemente \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh verbessern.

Fügen wir die Methode hinzu, die das grafische Element nach Namen zurückgibt:

//--- Return the list of graphical elements by chart ID and object name
   CArrayObj        *GetListCanvElementByName(const long chart_id,const string name)
                       {
                        string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                        CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL);
                        return CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                       }
//--- Return the graphical element by name
   CGCnvElement     *GetCanvElement(const string name)
                       {
                        string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                        CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                        return(list!=NULL ? list.At(0) : NULL);
                       }
//--- Return the graphical element by chart ID and name
   CGCnvElement     *GetCanvElement(const long chart_id,const string name)
                       {
                        CArrayObj *list=this.GetListCanvElementByName(chart_id,name);
                        return(list!=NULL ? list.At(0) : NULL);
                       }
//--- Return the graphical element by chart and object IDs

Da nun alle Namen aller grafischen Elemente auf der Leinwand eindeutig sind, muss bei der Suche nach einem Objekt nach Namen nicht mehr die ID des Charts angegeben werden, auf dem das Objekt aufgebaut ist. Daher ist es sinnvoll, eine Methode hinzuzufügen, die ein Objekt nur anhand seines Namens zurückgibt. Hier wird geprüft, welcher Name an die Methode übergeben wird, und wenn der Name des Programms in der Namenszeichenkette fehlt, wird der Programmname an den Anfang dieses Namens angehängt, damit der Name des gesuchten Objekts mit dem tatsächlichen Namen des grafischen Elements übereinstimmt. Schließlich enthalten sie alle eine Teilzeichenkette mit dem Namen des Programms. Auf diese Weise erstellt die Bibliothek die Namen der grafischen Elementobjekte.


WinForms-Objekte sind jetzt in der Lage, Nachrichten über ihre Ereignisse an die Steuerprogrammkarte zu senden. Diese Ereignisse werden von der Bibliothek abgefangen und sollten in der Lage sein, sie zu behandeln und die erforderliche Ereignismeldung an die erforderliche Klasse weiterzuleiten. Daher fügen wir die Ereignisbehandlung von WinForms-Objekten in den Event-Handler der Klasse der grafischen Kollektionsklasse ein:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   CGStdGraphObj *obj_std=NULL;  // Pointer to the standard graphical object
   CGCnvElement  *obj_cnv=NULL;  // Pointer to the graphical element object on canvas
   ushort idx=ushort(id-CHARTEVENT_CUSTOM);

//--- Handle WinForms control events
   if(idx>WF_CONTROL_EVENT_NO_EVENT && idx<WF_CONTROL_EVENTS_NEXT_CODE)
     {
      //--- Clicking the control
      if(idx==WF_CONTROL_EVENT_CLICK)
        {
         //---
        }
      //--- Selecting the TabControl tab
      if(idx==WF_CONTROL_EVENT_TAB_SELECT)
        {
         string array[];
         if(::StringSplit(sparam,::StringGetCharacter(";",0),array)!=2)
           {
            CMessage::ToLog(MSG_GRAPH_OBJ_FAILED_GET_OBJECT_NAMES);
            return;
           }
         CWinFormBase *main=this.GetCanvElement(array[0]);
         if(main==NULL)
            return;
         CWinFormBase *base=main.GetElementByName(array[1]);
         if(base!=NULL)
            base.OnChartEvent(idx,lparam,dparam,sparam);
        }
     }
//--- Handle the events of renaming and clicking a standard graphical object
   if(id==CHARTEVENT_OBJECT_CHANGE  || id==CHARTEVENT_OBJECT_DRAG    || id==CHARTEVENT_OBJECT_CLICK   ||
      idx==CHARTEVENT_OBJECT_CHANGE || idx==CHARTEVENT_OBJECT_DRAG   || idx==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Calculate the chart ID
      //---...
      //---...
     }
//---...
//---...
  }

Bisher wird hier nur ein Ereigniscode behandelt — das Auswählen einer Registerkarte in TabControl. Wir teilen die im Text-Parameter ‚sparam‘ übergebene Nachricht mit Hilfe der Funktion StringSplit() durch „;“ in zwei Teile. Als Ergebnis erhalten wir zwei Namen im Array — den Namen des Hauptobjekts (in diesem Fall das Panel-Objekt — main) und das TabControl WinForms-Objekt — base, das mit dem Panel verbunden ist. In den Parametern lparam und dparam übergeben wir den Zeilen- und Spaltenindex der ausgewählten Registerkartenüberschrift. Mit all diesen Daten können wir nun das Panel, an das TabControl angehängt ist, und die Registerkarte des Steuerelements, auf dessen Kopfzeile geklickt wurde, genau identifizieren. Nachdem wir Zeiger auf alle diese Objekte erhalten haben, können wir die Ereignisbehandlung des empfangenen Objekts aufrufen, der wiederum die Offset-Methode der Registerkarten-Kopfleiste aufruft.

Dies sind derzeit alle erforderlichen Änderungen und Verbesserungen.


Test

Um den Test durchzuführen, verwende ich den EA aus dem vorherigen Artikel und speichere ihn in \MQL5\Experts\TestDoEasy\Part118\ als TestDoEasy118.mq5.

Wir erstellen ein Hauptpanel und platzieren TabControl mit 11 Registerkarten darauf. Auf jeder der Registerkarten wird ein Textlabel mit einer Beschreibung dieser Registerkarte erstellt, damit wir während des Tests sehen können, welche Registerkarte tatsächlich angezeigt wird.

Legen Sie nach dem Start des Expert Advisors dessen Einstellungen fest, um die Registerkartenüberschriften an die Breite des Steuerelements anzupassen. Auf diese Weise wird deutlich, welche Registerkarte nicht passt und über den Rand des Behälters hinausgeht. Überprüfen Sie die Position der Registerkarten auf jeder Seite des Steuerelements — wie die Schaltflächen für die Bildlaufsteuerung in der Kopfleiste angeordnet sind. Wenn sich die Überschriften oben befinden, klicken Sie auf die rechts ausgeschnittenen Registerkartenüberschriften und sehen Sie, wie sich die gesamte Zeile nach links verschiebt, sodass die ausgewählte Überschrift sichtbar wird und das Verhalten von TabControl in MS Visual Studio nachgebildet wird.

Im OnInit() des EA erstellen wir ein ein Panel mit TabControl, das 11 Registerkarten mit Textbeschriftungen enthält:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set EA global variables
   ArrayResize(array_clr,2);        // Array of gradient filling colors
   array_clr[0]=C'26,100,128';      // Original ≈Dark-azure color
   array_clr[1]=C'35,133,169';      // Lightened original color
//--- Create the array with the current symbol and set it to be used in the library
   string array[1]={Symbol()};
   engine.SetUsedSymbols(array);
   //--- Create the timeseries object for the current symbol and period, and show its description in the journal
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions

//--- Create the required number of WinForms Panel objects
   CPanel *pnl=NULL;
   for(int i=0;i<1;i++)
     {
      pnl=engine.CreateWFPanel("WinForms Panel"+(string)i,(i==0 ? 50 : 70),(i==0 ? 50 : 70),410,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false);
      if(pnl!=NULL)
        {
         pnl.Hide();
         Print(DFUN,"Panel description: ",pnl.Description(),", Type and name: ",pnl.TypeElementDescription()," ",pnl.Name());
         //--- Set Padding to 4
         pnl.SetPaddingAll(3);
         //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs
         pnl.SetMovable(InpMovable);
         pnl.SetAutoSize(InpAutoSize,false);
         pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false);
   
         //--- Create TabControl
         pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,InpTabControlX,InpTabControlY,pnl.Width()-30,pnl.Height()-40,clrNONE,255,true,false);
         CTabControl *tc=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0);
         if(tc!=NULL)
           {
            tc.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode);
            tc.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment);
            tc.SetMultiline(InpTabCtrlMultiline);
            tc.SetHeaderPadding(6,0);
            tc.CreateTabPages(11,0,56,20,TextByLanguage("Вкладка","TabPage"));
            //--- Create a text label with a tab description on each tab
            for(int j=0;j<tc.TabPages();j++)
              {
               tc.CreateNewElement(j,GRAPH_ELEMENT_TYPE_WF_LABEL,60,20,80,20,clrDodgerBlue,255,true,false);
               CLabel *label=tc.GetTabElement(j,0);
               if(label==NULL)
                  continue;
               label.SetText("TabPage"+string(j+1));
              }
           }
        }
     }
//--- Display and redraw all created panels
   for(int i=0;i<1;i++)
     {
      pnl=engine.GetWFPanelByName("Panel"+(string)i);
      if(pnl!=NULL)
        {
         pnl.Show();
         pnl.Redraw(true);
        }
     }
        
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Die Panels werden in einer Schleife erstellt (im Moment gibt es nur ein Panel), denn es hat sich herausgestellt, dass, wenn Sie mehrere Panels mit TabControl-Steuerelementen erstellen, diese Steuerelemente nicht korrekt funktionieren. Um das zu beheben, werde ich die erforderliche Anzahl von Tafeln erstellen.

Kompilieren Sie den EA und führen Sie ihn auf dem Chart aus, nachdem Sie die erforderlichen Einstellungen vorgenommen haben:


Wir sehen, dass die angegebene Funktionen korrekt funktionieren.


Was kommt als Nächstes?

Im nächsten Artikel werde ich Methoden zum Blättern von Registerkartenüberschriften in alle Richtungen mithilfe der Bildlaufsteuertasten erstellen.

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 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
DoEasy. Steuerung (Teil 13): Optimierung der Interaktion von WinForms-Objekten mit der Maus, Beginn der Entwicklung des WinForms-Objekts TabControl
DoEasy. Steuerung (Teil 14): Neuer Algorithmus zur Benennung von grafischen Elementen. Fortsetzung der Arbeit am TabControl WinForms Objekt
DoEasy. Steuerung (Teil 15): TabControl WinForms Objekt — mehrere Reihen von Registerkartenüberschriften, Methoden zur Behandlung von Registerkarten
DoEasy. Steuerung (Teil 16): TabControl WinForms-Objekt — mehrere Reihen von Registerkarten-Kopfzeilen, Dehnung der Kopfzeilen zur Anpassung an den Container
DoEasy. Kontrollen (Teil 17): Beschneiden unsichtbarer Objektteile, Hilfspfeiltasten WinForms-Objekte