English Русский 中文 日本語 Português
preview
Die View Komponente für Tabellen im MQL5 MVC Paradigma: Grafisches Basiselement

Die View Komponente für Tabellen im MQL5 MVC Paradigma: Grafisches Basiselement

MetaTrader 5Beispiele |
12 0
Artyom Trishkin
Artyom Trishkin

Inhalt



Einführung

In der modernen Programmierung ist das MVC-Paradigma (Model-View-Controller) einer der beliebtesten Ansätze zur Entwicklung komplexer Anwendungen. Es ermöglicht die Aufteilung der Anwendungslogik in drei unabhängige Komponenten: Model (Datenmodell), View (Anzeige) und Controller. Dieser Ansatz vereinfacht die Entwicklung, das Testen und die Wartung des Codes und macht ihn besser strukturiert und lesbar.

Im Rahmen dieser Artikelserie betrachten wir den Prozess der Tabellenerstellung im MVC-Paradigma in MQL5. In den ersten beiden Artikeln haben wir Datenmodelle (Model) und die Basisarchitektur von Tabellen implementiert. Nun ist es an der Zeit, sich der Komponente View zuzuwenden, die für die Datenvisualisierung und zum Teil auch für die Nutzerinteraktion zuständig ist.

In diesem Artikel werden wir ein Basisobjekt zum Zeichnen auf der Leinwand implementieren, das die Grundlage für die Erstellung aller visuellen Komponenten von Tabellen und anderen Steuerelementen bildet. Das Objekt wird umfassen:

  • Farbmanagement in verschiedenen Zuständen (Standardzustand, Fokus, Tap, Lock);
  • Unterstützung für Transparenz und dynamische Größenänderung;
  • Begrenzung des Objektsauf die Containergrenzen;
  • Verwaltung der Sichtbarkeit und Blockierung von Objekten;
  • Aufteilung der Grafiken in zwei Ebenen: Hintergrund und Vordergrund.

Die Integration mit der bereits erstellten Komponente Modell wird hier nicht berücksichtigt. Darüber hinaus mit dem Controller-Komponente, die noch nicht erstellt worden ist, aber wir werden die Klassen in der Entwicklung unter Berücksichtigung der künftigen Integration zu entwerfen. Dies erleichtert die Verknüpfung von visuellen Elementen mit Daten und Steuerungslogik und gewährleistet eine vollständige Interaktion im Rahmen des MVC-Paradigmas. Als Ergebnis erhalten wir ein flexibles Werkzeug zur Erstellung von Tabellen und anderen grafischen Elementen, die wir in unseren Projekten verwenden können.

Da die Implementierung der Architektur der View-Komponente in MQL5 recht zeitaufwändig ist und viele Hilfsklassen und Vererbungen beinhaltet, wollen wir uns auf eine kurze Zusammenfassung einigen. Definieren wir eine Klasse, geben eine kurze Beschreibung der Klasse und betrachten anschließend kurz ihre Implementierung. Heute haben wir fünf solcher Klassen:

  1. eine Basisklasse für alle grafischen Objekte,
  2. eine Klasse für Farbmanagement,
  3. eine Klasse zur Verwaltung der Farben der verschiedenen Zustände eines grafischen Elements,
  4. rechteckige Bereichskontrollklasse,
  5. eine Basisklasse zum Zeichnen von grafischen Elementen auf der Leinwand.

Letztlich sind alle diese Klassen notwendig, damit die Basisklasse grafische Elemente zeichnen kann. Alle anderen Klassen, die bei der Implementierung verschiedener Steuerelemente, insbesondere des Table Controls, erstellt werden, erben von ihr.

Die ersten vier Klassen dieser Liste sind Hilfsklassen für die bequeme Implementierung der Funktionalität der Basisklasse zum Zeichnen von Grafikelementen (5), von der wir weiter erben werden, um alle Steuerelemente und ihre Komponenten zu erstellen.


Hilfsklassen

Falls noch nicht vorhanden, erstellen wir im Terminalverzeichnis \MQL5\Scripts\ einen neuen Ordner Tables\, und in diesem Ordner – den Ordner Controls. Sie speichert die Dateien, die im Rahmen der Artikel zur Erstellung der View-Komponente für Tabellen zu erstellen sind.

Erstellen wir in diesem neuen Ordner eine neue Include-Datei Base.mqh. Heute werden wir die Codes der Basisobjektklasse implementieren, um darin Steuerelemente zu erstellen. Vorläufige Implementierung von Makro-Substitutionen, Enumerationen und Funktionen:

//+------------------------------------------------------------------+
//|                                                         Base.mqh |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"

//+------------------------------------------------------------------+
//| Include libraries                                                |
//+------------------------------------------------------------------+
#include <Canvas\Canvas.mqh>              // CCanvas class
#include <Arrays\List.mqh>                // CList class

//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+
#define  clrNULL              0x00FFFFFF  // Transparent color for CCanvas
#define  MARKER_START_DATA    -1          // Data start marker in a file

//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+
enum ENUM_ELEMENT_TYPE                    // Enumeration of graphical element types
  {
   ELEMENT_TYPE_BASE = 0x10000,           // Basic object of graphical elements
   ELEMENT_TYPE_COLOR,                    // Color object
   ELEMENT_TYPE_COLORS_ELEMENT,           // Color object of the graphical object element
   ELEMENT_TYPE_RECTANGLE_AREA,           // Rectangular area of the element
   ELEMENT_TYPE_CANVAS_BASE,              // Basic canvas object for graphical elements
  };

enum ENUM_COLOR_STATE                     // Enumeration of element state colors
  {
   COLOR_STATE_DEFAULT,                   // Normal state color
   COLOR_STATE_FOCUSED,                   // Color when hovering over an element
   COLOR_STATE_PRESSED,                   // Color when clicking on an element
   COLOR_STATE_BLOCKED,                   // Blocked element color
  };
//+------------------------------------------------------------------+ 
//| Functions                                                        |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|  Return the element type as a string                             |
//+------------------------------------------------------------------+
string ElementDescription(const ENUM_ELEMENT_TYPE type)
  {
   string array[];
   int total=StringSplit(EnumToString(type),StringGetCharacter("_",0),array);
   string result="";
   for(int i=2;i<total;i++)
     {
      array[i]+=" ";
      array[i].Lower();
      array[i].SetChar(0,ushort(array[i].GetChar(0)-0x20));
      result+=array[i];
     }
   result.TrimLeft();
   result.TrimRight();
   return result;
  }
//+------------------------------------------------------------------+
//| Classes                                                          |
//+------------------------------------------------------------------+

In früheren Artikeln haben wir die Funktion implementiert, die den Objekttyp als String zurückgibt. Die Funktion, die den Kontrolltyp als Stringbeschreibung zurückgibt, ist absolut identisch mit der zuvor implementierten Funktion. Die Enumerationszeichenfolge wird einfach mit dem Trennzeichen „_“ in Teilzeichenfolgen unterteilt, und die daraus resultierenden Teilzeichenfolgen werden zur Erstellung der endgültigen Zeichenfolge verwendet.

Solange sich diese beiden identischen Funktionen in verschiedenen Dateien befinden, sollten wir sie beibehalten. Wenn wir dann alle Dateien zu einem einzigen Projekt zusammenfassen, werden wir beide Funktionen in eine einzige umwandeln, die den Namen nicht aus der Aufzählung, sondern aus der übergebenen Zeichenkette zurückgibt. In diesem Fall gibt derselbe Algorithmus den Namen des Objekts, Elements usw. auf dieselbe Weise zurück. Es ist wichtig, dass die Konstanten aller Enumerationen die gleiche Struktur des Konstantennamens enthalten: OBJECT_TYPE_XXX_YYY, ELEMENT_TYPE_XXX_YYY, ANYOTHER_TYPE_XXX_YYY_ZZZ ... In diesem Fall wird das, was in XXX_YYYY (XXX_YYY_ZZZ usw.) geschrieben ist, zurückgegeben, und das, was hier gelb markiert ist, wird abgeschnitten.

In allen Objekten der grafischen Elemente und in den Hilfsklassen enthält jedes von ihnen die gleichen Variablen und Methoden des Zugriffs auf sie – den Bezeichner und den Objektnamen. Anhand dieser Eigenschaften können die Elemente in den Listen durchsucht und sortiert werden. Es ist sinnvoll, diese Variablen und ihre Zugriffsmethoden in einer separaten Klasse unterzubringen, von der alle anderen Elemente erben werden.

Dies ist die Basisklasse der Grafikelementobjekte:

//+------------------------------------------------------------------+
//| Base class of graphical elements                                 |
//+------------------------------------------------------------------+
class CBaseObj : public CObject
  {
protected:
   int               m_id;                                     // ID
   ushort            m_name[];                                 // Name
   
public:
//--- Set (1) name and (2) ID
   void              SetName(const string name)                { ::StringToShortArray(name,this.m_name);    }
   void              SetID(const int id)                       { this.m_id=id;                              }
//--- Return (1) name and (2) ID
   string            Name(void)                          const { return ::ShortArrayToString(this.m_name);  }
   int               ID(void)                            const { return this.m_id;                          }

//--- Virtual (1) comparison and object type (2) methods
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_BASE); }
   
//--- Constructors/destructor
                     CBaseObj (void) : m_id(-1) {}
                    ~CBaseObj (void) {}
  };
//+------------------------------------------------------------------+
//| CBaseObj::Compare two objects                                    |
//+------------------------------------------------------------------+
int CBaseObj::Compare(const CObject *node,const int mode=0) const
  {
   const CBaseObj *obj=node;
   switch(mode)
     {
      case 0   :  return(this.Name()>obj.Name() ? 1 : this.Name()<obj.Name() ? -1 : 0);
      default  :  return(this.ID()>obj.ID()     ? 1 : this.ID()<obj.ID()     ? -1 : 0);
     }
  }

Kurzbeschreibung:

Eine Basisklasse für alle grafischen Objekte:

  • enthält allgemeine Eigenschaften wie Bezeichner (m_id) und Name (m_name),
  • bietet grundlegende Methoden zum Vergleichen von Objekten (Compare) und zum Abrufen des Objekttyps (Type),
  • wird sie als übergeordnete Klasse für alle anderen Klassen verwendet, um die Einheitlichkeit der Hierarchie zu gewährleisten.

Die Klasse bietet einen minimalen Satz von Eigenschaften, die jedem der Objekte der grafischen Elemente eigen sind. Das Erben von dieser Klasse erspart es uns, diese Variablen in jeder neuen Klasse zu deklarieren und Zugriffsmethoden für diese Variablen zu implementieren – diese Variablen und Methoden werden in den geerbten Klassen verfügbar sein. Dementsprechend wird die Compare-Methode für jedes der vom Basisobjekt geerbten Objekte eine Funktion zum Suchen und Sortieren nach diesen beiden Eigenschaften bereitstellen.



Farbmanagement-Klassen

Bei der Erstellung der Controller-Komponente muss ein visuelles Design für die Interaktion des Nutzers mit grafischen Elementen erstellt werden. Eine Möglichkeit, die Aktivität eines Objekts zu zeigen, besteht darin, seine Farbe zu ändern, wenn der Mauszeiger über den Bereich des grafischen Elements bewegt wird, seine Reaktion auf Mausklicks oder die Software-Sperre.

Jedes Objekt besteht aus drei Elementen, die bei verschiedenen Nutzerinteraktionen ihre Farbe ändern können: die Farbe des Hintergrunds, des Rahmens und des Texts. Jedes dieser drei Elemente kann einen eigenen Satz von Farben für verschiedene Zustände enthalten. Um die Farbe eines Elements sowie die Farben aller Elemente, die ihre Farbe ändern, bequem zu steuern, implementieren wir zwei Hilfsklassen.

Farbe Klasse

//+------------------------------------------------------------------+
//| Color class                                                      |
//+------------------------------------------------------------------+
class CColor : public CBaseObj
  {
protected:
   color             m_color;                                  // Color
   
public:
//--- Set color
   bool              SetColor(const color clr)
                       {
                        if(this.m_color==clr)
                           return false;
                        this.m_color=clr;
                        return true;
                       }
//--- Return color
   color             Get(void)                           const { return this.m_color;              }

//--- (1) Return and (2) display the object description in the journal
   virtual string    Description(void);
   void              Print(void);
   
//--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_COLOR);       }
   
//--- Constructors/destructor
                     CColor(void) : m_color(clrNULL)                          { this.SetName("");  }
                     CColor(const color clr) : m_color(clr)                   { this.SetName("");  }
                     CColor(const color clr,const string name) : m_color(clr) { this.SetName(name);}
                    ~CColor(void) {}
  };
//+------------------------------------------------------------------+
//| CColor::Return the object description                            |
//+------------------------------------------------------------------+
string CColor::Description(void)
  {
   string color_name=(this.Get()!=clrNULL ? ::ColorToString(this.Get(),true) : "clrNULL (0x00FFFFFF)");
   return(this.Name()+(this.Name()!="" ? " " : "")+"Color: "+color_name);
  }
//+------------------------------------------------------------------+
//| CColor::Display object description in the journal                |
//+------------------------------------------------------------------+
void CColor::Print(void)
  {
   ::Print(this.Description());
  }
//+------------------------------------------------------------------+
//| CColor::Save to file                                             |
//+------------------------------------------------------------------+
bool CColor::Save(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Save data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,-1)!=sizeof(long))
      return false;
//--- Save the object type
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return false;

//--- Save the color
   if(::FileWriteInteger(file_handle,this.m_color,INT_VALUE)!=INT_VALUE)
      return false;
//--- Save the ID
   if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE)
      return false;
//--- Save the name
   if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
   
//--- All is successful
   return true;
  }
//+------------------------------------------------------------------+
//| CColor::Load from file                                           |
//+------------------------------------------------------------------+
bool CColor::Load(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=-1)
      return false;
//--- Load the object type
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return false;

//--- Load the color
   this.m_color=(color)::FileReadInteger(file_handle,INT_VALUE);
//--- Load the ID
   this.m_id=::FileReadInteger(file_handle,INT_VALUE);
//--- Load the name
   if(::FileReadArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
   
//--- All is successful
   return true;
  }

Kurze Beschreibung der Klasse:

Farbmanagement-Klasse:

  • speichert die Farbe als einen Wert vom Typ color (m_color),
  • bietet Methoden zum Setzen und Abrufen einer Farbe (SetColor, Get),
  • implementiert Methoden zum Speichern und Laden von Farben in/aus einer Datei (Speichern, Laden),
  • kann zur Darstellung jeder Farbe in grafischen Elementen verwendet werden.

Die Farbklasse des grafischen Objektelements

//+------------------------------------------------------------------+
//| Color class of a graphical object element                        |
//+------------------------------------------------------------------+
class CColorElement : public CBaseObj
  {
protected:
   CColor            m_current;                                // Current color. It could be one of the following
   CColor            m_default;                                // Normal state color
   CColor            m_focused;                                // Color on hover
   CColor            m_pressed;                                // Color on click
   CColor            m_blocked;                                // Blocked element color
   
//--- Convert RGB to color
   color             RGBToColor(const double r,const double g,const double b) const;
//--- Write RGB component values to variables
   void              ColorToRGB(const color clr,double &r,double &g,double &b);
//--- Return (1) Red, (2) Green, (3) Blue color components
   double            GetR(const color clr)                     { return clr&0xFF;                           }
   double            GetG(const color clr)                     { return(clr>>8)&0xFF;                       }
   double            GetB(const color clr)                     { return(clr>>16)&0xFF;                      }
   
public:
//--- Return a new color
   color             NewColor(color base_color, int shift_red, int shift_green, int shift_blue);

//--- Initialize colors for different states
   bool              InitDefault(const color clr)              { return this.m_default.SetColor(clr);       }
   bool              InitFocused(const color clr)              { return this.m_focused.SetColor(clr);       }
   bool              InitPressed(const color clr)              { return this.m_pressed.SetColor(clr);       }
   bool              InitBlocked(const color clr)              { return this.m_blocked.SetColor(clr);       }
   
//--- Set colors for all states
   void              InitColors(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked);
   void              InitColors(const color clr);
    
//--- Return colors of different states
   color             GetCurrent(void)                    const { return this.m_current.Get();               }
   color             GetDefault(void)                    const { return this.m_default.Get();               }
   color             GetFocused(void)                    const { return this.m_focused.Get();               }
   color             GetPressed(void)                    const { return this.m_pressed.Get();               }
   color             GetBlocked(void)                    const { return this.m_blocked.Get();               }
   
//--- Set one of the colors from the list as the current one
   bool              SetCurrentAs(const ENUM_COLOR_STATE color_state);

//--- (1) Return and (2) display the object description in the journal
   virtual string    Description(void);
   void              Print(void);
   
//--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_COLORS_ELEMENT);       }
   
//--- Constructors/destructor
                     CColorElement(void);
                     CColorElement(const color clr);
                     CColorElement(const color clr_default,const color clr_focused,const color clr_pressed,const color clr_blocked);
                    ~CColorElement(void) {}
  };
//+-----------------------------------------------------------------------------+
//| CColorControl::Constructor with setting the transparent colors of the object|
//+-----------------------------------------------------------------------------+
CColorElement::CColorElement(void)
  {
   this.InitColors(clrNULL,clrNULL,clrNULL,clrNULL);
   this.m_default.SetName("Default"); this.m_default.SetID(1);
   this.m_focused.SetName("Focused"); this.m_focused.SetID(2);
   this.m_pressed.SetName("Pressed"); this.m_pressed.SetID(3);
   this.m_blocked.SetName("Blocked"); this.m_blocked.SetID(4);
   this.SetCurrentAs(COLOR_STATE_DEFAULT);
   this.m_current.SetName("Current");
   this.m_current.SetID(0);
  }
//+------------------------------------------------------------------+
//| CColorControl::Constructor specifying the object colors          |
//+------------------------------------------------------------------+
CColorElement::CColorElement(const color clr_default,const color clr_focused,const color clr_pressed,const color clr_blocked)
  {
   this.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
   this.m_default.SetName("Default"); this.m_default.SetID(1);
   this.m_focused.SetName("Focused"); this.m_focused.SetID(2);
   this.m_pressed.SetName("Pressed"); this.m_pressed.SetID(3);
   this.m_blocked.SetName("Blocked"); this.m_blocked.SetID(4);
   this.SetCurrentAs(COLOR_STATE_DEFAULT);
   this.m_current.SetName("Current");
   this.m_current.SetID(0);
  }
//+------------------------------------------------------------------+
//| CColorControl::Constructor specifying the object colors          |
//+------------------------------------------------------------------+
CColorElement::CColorElement(const color clr)
  {
   this.InitColors(clr);
   this.m_default.SetName("Default"); this.m_default.SetID(1);
   this.m_focused.SetName("Focused"); this.m_focused.SetID(2);
   this.m_pressed.SetName("Pressed"); this.m_pressed.SetID(3);
   this.m_blocked.SetName("Blocked"); this.m_blocked.SetID(4);
   this.SetCurrentAs(COLOR_STATE_DEFAULT);
   this.m_current.SetName("Current");
   this.m_current.SetID(0);
  }
//+------------------------------------------------------------------+
//| CColorControl::Set colors for all states                         |
//+------------------------------------------------------------------+
void CColorElement::InitColors(const color clr_default,const color clr_focused,const color clr_pressed,const color clr_blocked)
  {
   this.InitDefault(clr_default);
   this.InitFocused(clr_focused);
   this.InitPressed(clr_pressed);
   this.InitBlocked(clr_blocked);   
  }
//+----------------------------------------------------------------------+
//| CColorControl::Set the colors for all states based on the current one|
//+----------------------------------------------------------------------+
void CColorElement::InitColors(const color clr)
  {
   this.InitDefault(clr);
   this.InitFocused(this.NewColor(clr,-3,-3,-3));
   this.InitPressed(this.NewColor(clr,-6,-6,-6));
   this.InitBlocked(clrSilver);   
  }
//+---------------------------------------------------------------------+
//|CColorControl::Set one of the colors from the list as the current one|
//+---------------------------------------------------------------------+
bool CColorElement::SetCurrentAs(const ENUM_COLOR_STATE color_state)
  {
   switch(color_state)
     {
      case COLOR_STATE_DEFAULT   :  return this.m_current.SetColor(this.m_default.Get());
      case COLOR_STATE_FOCUSED   :  return this.m_current.SetColor(this.m_focused.Get());
      case COLOR_STATE_PRESSED   :  return this.m_current.SetColor(this.m_pressed.Get());
      case COLOR_STATE_BLOCKED   :  return this.m_current.SetColor(this.m_blocked.Get());
      default                    :  return false;
     }
  }
//+------------------------------------------------------------------+
//| CColorControl::Convert RGB to color                              |
//+------------------------------------------------------------------+
color CColorElement::RGBToColor(const double r,const double g,const double b) const
  {
   int int_r=(int)::round(r);
   int int_g=(int)::round(g);
   int int_b=(int)::round(b);
   int clr=0;
   clr=int_b;
   clr<<=8;
   clr|=int_g;
   clr<<=8;
   clr|=int_r;

   return (color)clr;
  }
//+------------------------------------------------------------------+
//| CColorControl::Get RGB component values                          |
//+------------------------------------------------------------------+
void CColorElement::ColorToRGB(const color clr,double &r,double &g,double &b)
  {
   r=this.GetR(clr);
   g=this.GetG(clr);
   b=this.GetB(clr);
  }
//+------------------------------------------------------------------+
//| CColorControl::Return color with a new color component           |
//+------------------------------------------------------------------+
color CColorElement::NewColor(color base_color, int shift_red, int shift_green, int shift_blue)
  {
   double clrR=0, clrG=0, clrB=0;
   this.ColorToRGB(base_color,clrR,clrG,clrB);
   double clrRx=(clrR+shift_red  < 0 ? 0 : clrR+shift_red  > 255 ? 255 : clrR+shift_red);
   double clrGx=(clrG+shift_green< 0 ? 0 : clrG+shift_green> 255 ? 255 : clrG+shift_green);
   double clrBx=(clrB+shift_blue < 0 ? 0 : clrB+shift_blue > 255 ? 255 : clrB+shift_blue);
   return this.RGBToColor(clrRx,clrGx,clrBx);
  }
//+------------------------------------------------------------------+
//| CColorElement::Return the object description                     |
//+------------------------------------------------------------------+
string CColorElement::Description(void)
  {
   string res=::StringFormat("%s Colors. %s",this.Name(),this.m_current.Description());
   res+="\n  1: "+this.m_default.Description();
   res+="\n  2: "+this.m_focused.Description();
   res+="\n  3: "+this.m_pressed.Description();
   res+="\n  4: "+this.m_blocked.Description();
   return res;
  }
//+------------------------------------------------------------------+
//| CColorElement::Display object description in the journal         |
//+------------------------------------------------------------------+
void CColorElement::Print(void)
  {
   ::Print(this.Description());
  }
//+------------------------------------------------------------------+
//| CColorElement::Save to file                                      |
//+------------------------------------------------------------------+
bool CColorElement::Save(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Save data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,-1)!=sizeof(long))
      return false;
//--- Save the object type
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return false;
   
//--- Save the ID
   if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE)
      return false;
//--- Save the name of the element colors
   if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
//--- Save the current color
   if(!this.m_current.Save(file_handle))
      return false;
//--- Save the color of the normal state
   if(!this.m_default.Save(file_handle))
      return false;
//--- Save the color when hovering the cursor
   if(!this.m_focused.Save(file_handle))
      return false;
//--- Save the color when clicking
   if(!this.m_pressed.Save(file_handle))
      return false;
//--- Save the color of the blocked element
   if(!this.m_blocked.Save(file_handle))
      return false;
   
//--- All is successful
   return true;
  }
//+------------------------------------------------------------------+
//| CColorElement::Load from file                                    |
//+------------------------------------------------------------------+
bool CColorElement::Load(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=-1)
      return false;
//--- Load the object type
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return false;
   
//--- Load the ID
   this.m_id=::FileReadInteger(file_handle,INT_VALUE);
//--- Load the name of the element colors
   if(::FileReadArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
//--- Load the current color
   if(!this.m_current.Load(file_handle))
      return false;
//--- Load the normal state color
   if(!this.m_default.Load(file_handle))
      return false;
//--- Load the color on hover
   if(!this.m_focused.Load(file_handle))
      return false;
//--- Load the color on click
   if(!this.m_pressed.Load(file_handle))
      return false;
//--- Load the color of the blocked element
   if(!this.m_blocked.Load(file_handle))
      return false;
   
//--- All is successful
   return true;
  }

Kurze Beschreibung der Klasse:

Eine Klasse zur Verwaltung der Farben der verschiedenen Zustände eines grafischen Elements:

  • speichert Farben für vier Zustände: normal (m_default), Fokus (m_focused), gedrückt (m_pressed) und blockiert (m_blocked),
  • unterstützt die aktuelle Farbe (m_current), die auf einen der Zustände gesetzt werden kann,
  • bietet Methoden zur Initialisierung der Farben aller Zustände (InitColors) und zum Umschalten der aktuellen Farbe (SetCurrentAs),
  • enthält eine Methode, um eine neue Farbe aus der Grundfarbe zu erhalten, wobei die Offsets der Farbkomponenten (NewColor) angegeben werden,
  • implementiert Methoden zum Speichern und Laden aller Farben in/aus einer Datei (Speichern, Laden),
  • ist nützlich für die Erstellung interaktiver Elemente wie Schaltflächen, Tabellenzeilen oder Zellen.



Steuerklasse Rechteckige Fläche

Im Terminal wird eine interessante CRect-Struktur in der Datei Rect.mqh im Ordner \MQL5\Include\Controls\ dargestellt. Sie bietet eine Reihe von Methoden zur Steuerung eines rechteckigen Fensters, das einen bestimmten Umriss auf einem grafischen Element einrahmen kann. Mit diesen Umrissen können wir mehrere Bereiche eines einzelnen Elements virtuell markieren und deren Koordinaten und Grenzen verfolgen. So können wir die Koordinaten des Mauszeigers auf den hervorgehobenen Bereichen verfolgen und die Mausinteraktion mit dem Bereich des Grafikelements organisieren.

Das einfachste Beispiel sind die Ränder des gesamten grafischen Elements. Ein weiteres Beispiel für einen definierten Bereich ist der Bereich für die Statusleiste, die Bildlaufleisten oder den Tabellenkopf.

Mit Hilfe der vorgestellten Struktur können wir ein spezielles Objekt erstellen, das es ermöglicht, einen rechteckigen Bereich auf einem grafischen Element festzulegen. Und eine Liste solcher Objekte ermöglicht es Ihnen, mehrere Überwachungsbereiche auf einem einzigen grafischen Element zu speichern, wobei jeder Bereich für seine eigenen Zwecke bestimmt ist und der Zugriff über den Namen oder die Kennung des Bereichs erfolgt.

Um solche Strukturen in Objektlisten zu speichern, müssen wir ein von CObject geerbtes Objekt erstellen. Darin wird diese Struktur erklärt:

//+------------------------------------------------------------------+
//| Rectangular region class                                         |
//+------------------------------------------------------------------+
class CBound : public CBaseObj
  {
protected:
   CRect             m_bound;                                  // Rectangular area structure

public:
//--- Change the bounding rectangular (1) width, (2) height and (3) size
   void              ResizeW(const int size)                   { this.m_bound.Width(size);                        }
   void              ResizeH(const int size)                   { this.m_bound.Height(size);                       }
   void              Resize(const int w,const int h)           { this.m_bound.Width(w); this.m_bound.Height(h);   }
   
//--- Set (1) X, (2) Y and (3) both coordinates of the bounding rectangle
   void              SetX(const int x)                         { this.m_bound.left=x;                             }
   void              SetY(const int y)                         { this.m_bound.top=y;                              }
   void              SetXY(const int x,const int y)            { this.m_bound.LeftTop(x,y);                       }
   
//--- (1) Set and (2) shift the bounding rectangle by the specified coordinates/offset size
   void              Move(const int x,const int y)             { this.m_bound.Move(x,y);                          }
   void              Shift(const int dx,const int dy)          { this.m_bound.Shift(dx,dy);                       }
   
//--- Returns the object coordinates, dimensions, and boundaries
   int               X(void)                             const { return this.m_bound.left;                        }
   int               Y(void)                             const { return this.m_bound.top;                         }
   int               Width(void)                         const { return this.m_bound.Width();                     }
   int               Height(void)                        const { return this.m_bound.Height();                    }
   int               Right(void)                         const { return this.m_bound.right-1;                     }
   int               Bottom(void)                        const { return this.m_bound.bottom-1;                    }
   
//--- (1) Return and (2) display the object description in the journal
   virtual string    Description(void);
   void              Print(void);
   
//--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_RECTANGLE_AREA);             }
   
//--- Constructors/destructor
                     CBound(void) { ::ZeroMemory(this.m_bound); }
                     CBound(const int x,const int y,const int w,const int h) { this.SetXY(x,y); this.Resize(w,h); }
                    ~CBound(void) { ::ZeroMemory(this.m_bound); }
  };
//+------------------------------------------------------------------+
//| CBound::Return the object description                            |
//+------------------------------------------------------------------+
string CBound::Description(void)
  {
   string nm=this.Name();
   string name=(nm!="" ? ::StringFormat(" \"%s\"",nm) : nm);
   return ::StringFormat("%s%s: x %d, y %d, w %d, h %d",
                         ElementDescription((ENUM_ELEMENT_TYPE)this.Type()),name,
                         this.X(),this.Y(),this.Width(),this.Height());
  }
//+------------------------------------------------------------------+
//| CBound::Display the object description in the journal            |
//+------------------------------------------------------------------+
void CBound::Print(void)
  {
   ::Print(this.Description());
  }
//+------------------------------------------------------------------+
//| CBound::Save to file                                             |
//+------------------------------------------------------------------+
bool CBound::Save(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Save data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,-1)!=sizeof(long))
      return false;
//--- Save the object type
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return false;
   
//--- Save the ID
   if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE)
      return false;
//--- Save the name
   if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
   //--- Save the area structure
   if(::FileWriteStruct(file_handle,this.m_bound)!=sizeof(this.m_bound))
      return(false);
   
//--- All is successful
   return true;
  }
//+------------------------------------------------------------------+
//| CBound::Load from file                                           |
//+------------------------------------------------------------------+
bool CBound::Load(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=-1)
      return false;
//--- Load the object type
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return false;
   
//--- Load the ID
   this.m_id=::FileReadInteger(file_handle,INT_VALUE);
//--- Load the name
   if(::FileReadArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
   //--- Load the region structure
   if(::FileReadStruct(file_handle,this.m_bound)!=sizeof(this.m_bound))
      return(false);
   
//--- All is successful
   return true;
  }

Kurze Beschreibung der Klasse:

Steuerklasse für rechteckige Flächen:

  • speichert die Grenzen eines Bereichs als CRect-Struktur (m_bound),
  • bietet Methoden zur Größenänderung (Resize, ResizeW, ResizeH), zum Setzen von Koordinaten (SetX, SetY, SetXY) und zum Verschieben des Bereichs (Move, Shift),
  • ermöglicht das Abrufen von Koordinaten und Abmessungen des Bereichs (X, Y, Breite, Höhe, Rechts, Unten),
  • implementiert Methoden zum Speichern und Laden des Bereichs in/aus einer Datei (Speichern, Laden),
  • wird verwendet, um die Grenzen von Grafikelementen oder rechteckige Bereiche in ihnen zu definieren.

Mit Hilfe der oben beschriebenen Hilfsklassen können wir mit der Erstellung einer Basisklasse für alle grafischen Elemente beginnen.



Zeichnen der Basisklasse

Die Klasse wird recht umfangreich sein, daher sollten Sie, bevor wir mit der Erstellung beginnen, zum besseren Verständnis die Kurzbeschreibung lesen:

Eine Basisklasse für die Bearbeitung von grafischen Elementen auf der Leinwand:

  • enthält zwei Leinwände: für den Hintergrund (m_background) und für den Vordergrund (m_foreground),
  • speichert Elementgrenzen (m_bound) sowie Informationen über den Container (m_container),
  • unterstützt das Farbmanagement über CColorElement-Objekte für Hintergrund, Vordergrund und Rand,
  • implementiert Methoden zur Verwaltung der Sichtbarkeit (Hide, Show), des Blockierens (Block, Unblock) und des Trimmens entlang der Containergrenzen (ObjectTrim),
  • unterstützt dynamische Größen- und Koordinatenänderungen (ObjectResize, ObjectSetX, ObjectSetY),
  • bietet Methoden zum Zeichnen (Draw), Aktualisieren (Update) und Löschen (Clear) eines grafischen Elements,
  • implementiert Methoden zum Speichern und Laden eines Objekts in/aus einer Datei (Save, Load),
  • ist die Grundlage für die Erstellung komplexer grafischer Elemente wie Tabellenzellen, Zeilen und Überschriften.

Die Basisklasse der grafischen Elemente Canvas

//+------------------------------------------------------------------+
//| Base class of graphical elements canvas                          |
//+------------------------------------------------------------------+
class CCanvasBase : public CBaseObj
  {
protected:
   CCanvas           m_background;                             // Background canvas
   CCanvas           m_foreground;                             // Foreground canvas
   CBound            m_bound;                                  // Object boundaries
   CCanvasBase      *m_container;                              // Parent container object
   CColorElement     m_color_background;                       // Background color control object
   CColorElement     m_color_foreground;                       // Foreground color control object
   CColorElement     m_color_border;                           // Border color control object
   long              m_chart_id;                               // Chart ID
   int               m_wnd;                                    // Chart subwindow index
   int               m_wnd_y;                                  // Cursor Y coordinate offset in the subwindow
   int               m_obj_x;                                  // Graphical object X coordinate
   int               m_obj_y;                                  // Graphical object Y coordinate
   uchar             m_alpha;                                  // Transparency
   uint              m_border_width;                           // Frame width
   string            m_program_name;                           // Program name
   bool              m_hidden;                                 // Hidden object flag
   bool              m_blocked;                                // Blocked element flag
   bool              m_focused;                                // Element flag in focus
   
private:
//--- Return the offset of the initial drawing coordinates on the canvas relative to the canvas and the object coordinates
   int               CanvasOffsetX(void)                 const { return(this.ObjectX()-this.X());                                                  }
   int               CanvasOffsetY(void)                 const { return(this.ObjectY()-this.Y());                                                  }
//--- Return the adjusted coordinate of a point on the canvas, taking into account the offset of the canvas relative to the object
   int               AdjX(const int x)                   const { return(x-this.CanvasOffsetX());                                                   }
   int               AdjY(const int y)                   const { return(y-this.CanvasOffsetY());                                                   }
   
protected:
//--- Returns the adjusted chart ID
   long              CorrectChartID(const long chart_id) const { return(chart_id!=0 ? chart_id : ::ChartID());                                     }

//--- Get the boundaries of the parent container object
   int               ContainerLimitLeft(void)            const { return(this.m_container==NULL ? this.X()      :  this.m_container.LimitLeft());   }
   int               ContainerLimitRight(void)           const { return(this.m_container==NULL ? this.Right()  :  this.m_container.LimitRight());  }
   int               ContainerLimitTop(void)             const { return(this.m_container==NULL ? this.Y()      :  this.m_container.LimitTop());    }
   int               ContainerLimitBottom(void)          const { return(this.m_container==NULL ? this.Bottom() :  this.m_container.LimitBottom()); }
   
//--- Return the graphical object coordinates, boundaries and dimensions
   int               ObjectX(void)                       const { return this.m_obj_x;                                                              }
   int               ObjectY(void)                       const { return this.m_obj_y;                                                              }
   int               ObjectWidth(void)                   const { return this.m_background.Width();                                                 }
   int               ObjectHeight(void)                  const { return this.m_background.Height();                                                }
   int               ObjectRight(void)                   const { return this.ObjectX()+this.ObjectWidth()-1;                                       }
   int               ObjectBottom(void)                  const { return this.ObjectY()+this.ObjectHeight()-1;                                      }
   
//--- Change the bounding rectangular (1) width, (2) height and (3) size
   void              BoundResizeW(const int size)              { this.m_bound.ResizeW(size);                                                       }
   void              BoundResizeH(const int size)              { this.m_bound.ResizeH(size);                                                       }
   void              BoundResize(const int w,const int h)      { this.m_bound.Resize(w,h);                                                         }
   
//--- Set (1) X, (2) Y and (3) both coordinates of the bounding rectangle
   void              BoundSetX(const int x)                    { this.m_bound.SetX(x);                                                             }
   void              BoundSetY(const int y)                    { this.m_bound.SetY(y);                                                             }
   void              BoundSetXY(const int x,const int y)       { this.m_bound.SetXY(x,y);                                                          }
   
//--- (1) Set and (2) shift the bounding rectangle by the specified coordinates/offset size
   void              BoundMove(const int x,const int y)        { this.m_bound.Move(x,y);                                                           }
   void              BoundShift(const int dx,const int dy)     { this.m_bound.Shift(dx,dy);                                                        }
   
//--- Change the graphical object (1) width, (2) height and (3) size
   bool              ObjectResizeW(const int size);
   bool              ObjectResizeH(const int size);
   bool              ObjectResize(const int w,const int h);
   
//--- Set the graphical object (1) X, (2) Y and (3) both coordinates
   bool              ObjectSetX(const int x);
   bool              ObjectSetY(const int y);
   bool              ObjectSetXY(const int x,const int y)      { return(this.ObjectSetX(x) && this.ObjectSetY(y));                                 }
   
//--- (1) Set and (2) relocate the graphical object by the specified coordinates/offset size
   bool              ObjectMove(const int x,const int y)       { return this.ObjectSetXY(x,y);                                                     }
   bool              ObjectShift(const int dx,const int dy)    { return this.ObjectSetXY(this.ObjectX()+dx,this.ObjectY()+dy);                     }
   
//--- Limit the graphical object by the container dimensions
   virtual void      ObjectTrim(void);
   
public:
//--- Return the pointer to the canvas (1) background and (2) foreground
   CCanvas          *GetBackground(void)                       { return &this.m_background;                                                        }
   CCanvas          *GetForeground(void)                       { return &this.m_foreground;                                                        }
   
//--- Return the pointer to the color management object for the (1) background, (2) foreground and (3) border
   CColorElement    *GetBackColorControl(void)                 { return &this.m_color_background;                                                  }
   CColorElement    *GetForeColorControl(void)                 { return &this.m_color_foreground;                                                  }
   CColorElement    *GetBorderColorControl(void)               { return &this.m_color_border;                                                      }
   
//--- Return the (1) background, (2) foreground and (3) border color
   color             BackColor(void)                     const { return this.m_color_background.GetCurrent();                                      }
   color             ForeColor(void)                     const { return this.m_color_foreground.GetCurrent();                                      }
   color             BorderColor(void)                   const { return this.m_color_border.GetCurrent();                                          }
   
//--- Set background colors for all states
   void              InitBackColors(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked)
                       {
                        this.m_color_background.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
                       }
   void              InitBackColors(const color clr)           { this.m_color_background.InitColors(clr);                                          }

//--- Set foreground colors for all states
   void              InitForeColors(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked)
                       {
                        this.m_color_foreground.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
                       }
   void              InitForeColors(const color clr)           { this.m_color_foreground.InitColors(clr);                                          }

//--- Set border colors for all states
   void              InitBorderColors(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked)
                       {
                        this.m_color_border.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
                       }
   void              InitBorderColors(const color clr)         { this.m_color_border.InitColors(clr);                                              }

//--- Initialize the color of (1) background, (2) foreground and (3) frame with initial values
   void              InitBackColorDefault(const color clr)     { this.m_color_background.InitDefault(clr);                                         }
   void              InitForeColorDefault(const color clr)     { this.m_color_foreground.InitDefault(clr);                                         }
   void              InitBorderColorDefault(const color clr)   { this.m_color_border.InitDefault(clr);                                             }
   
//--- Initialize the color of (1) background, (2) foreground and (3) frame on hover with initial values
   void              InitBackColorFocused(const color clr)     { this.m_color_background.InitFocused(clr);                                         }
   void              InitForeColorFocused(const color clr)     { this.m_color_foreground.InitFocused(clr);                                         }
   void              InitBorderColorFocused(const color clr)   { this.m_color_border.InitFocused(clr);                                             }
   
//--- Initialize the color of (1) background, (2) foreground and (3) frame on click with initial values
   void              InitBackColorPressed(const color clr)     { this.m_color_background.InitPressed(clr);                                         }
   void              InitForeColorPressed(const color clr)     { this.m_color_foreground.InitPressed(clr);                                         }
   void              InitBorderColorPressed(const color clr)   { this.m_color_border.InitPressed(clr);                                             }
   
//--- Initialize the color of (1) background, (2) foreground and (3) frame of a blocked object with initial values
   void              InitBackColorBlocked(const color clr)     { this.m_color_background.InitBlocked(clr);                                         }
   void              InitForeColorBlocked(const color clr)     { this.m_color_foreground.InitBlocked(clr);                                         }
   void              InitBorderColorBlocked(const color clr)   { this.m_color_border.InitBlocked(clr);                                             }
   
//--- Set the current background color to different states
   bool              BackColorToDefault(void)                  { return this.m_color_background.SetCurrentAs(COLOR_STATE_DEFAULT);                 }
   bool              BackColorToFocused(void)                  { return this.m_color_background.SetCurrentAs(COLOR_STATE_FOCUSED);                 }
   bool              BackColorToPressed(void)                  { return this.m_color_background.SetCurrentAs(COLOR_STATE_PRESSED);                 }
   bool              BackColorToBlocked(void)                  { return this.m_color_background.SetCurrentAs(COLOR_STATE_BLOCKED);                 }
   
//--- Set the current foreground color to different states
   bool              ForeColorToDefault(void)                  { return this.m_color_foreground.SetCurrentAs(COLOR_STATE_DEFAULT);                 }
   bool              ForeColorToFocused(void)                  { return this.m_color_foreground.SetCurrentAs(COLOR_STATE_FOCUSED);                 }
   bool              ForeColorToPressed(void)                  { return this.m_color_foreground.SetCurrentAs(COLOR_STATE_PRESSED);                 }
   bool              ForeColorToBlocked(void)                  { return this.m_color_foreground.SetCurrentAs(COLOR_STATE_BLOCKED);                 }
   
//--- Set the current frame color to different states
   bool              BorderColorToDefault(void)                { return this.m_color_border.SetCurrentAs(COLOR_STATE_DEFAULT);                     }
   bool              BorderColorToFocused(void)                { return this.m_color_border.SetCurrentAs(COLOR_STATE_FOCUSED);                     }
   bool              BorderColorToPressed(void)                { return this.m_color_border.SetCurrentAs(COLOR_STATE_PRESSED);                     }
   bool              BorderColorToBlocked(void)                { return this.m_color_border.SetCurrentAs(COLOR_STATE_BLOCKED);                     }
   
//--- Set the current colors of the element to different states
   bool              ColorsToDefault(void);
   bool              ColorsToFocused(void);
   bool              ColorsToPressed(void);
   bool              ColorsToBlocked(void);
   
//--- Set the pointer to the parent container object
   void              SetContainerObj(CCanvasBase *obj);
   
//--- Create OBJ_BITMAP_LABEL
   bool              Create(const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h);

//--- Return (1) the object's belonging to the program, the flag (2) of a hidden element, (3) a blocked element and (4) the graphical object name
   bool              IsBelongsToThis(void)   const { return(::ObjectGetString(this.m_chart_id,this.NameBG(),OBJPROP_TEXT)==this.m_program_name);   }
   bool              IsHidden(void)          const { return this.m_hidden;                                                                         }
   bool              IsBlocked(void)         const { return this.m_blocked;                                                                        }
   bool              IsFocused(void)         const { return this.m_focused;                                                                        }
   string            NameBG(void)    const { return this.m_background.ChartObjectName();                                                           }
   string            NameFG(void)    const { return this.m_foreground.ChartObjectName();                                                           }
   
//--- (1) Return and (2) set transparency
   uchar             Alpha(void)                         const { return this.m_alpha;                                                              }
   void              SetAlpha(const uchar value)               { this.m_alpha=value;                                                               }
   
//--- (1) Return and (2) set the frame width
    uint             BorderWidth(void)                   const { return this.m_border_width;                                                       } 
    void             SetBorderWidth(const uint width)          { this.m_border_width=width;                                                        }
                      
//--- Returns the object coordinates, dimensions, and boundaries
   int               X(void)                             const { return this.m_bound.X();                                                          }
   int               Y(void)                             const { return this.m_bound.Y();                                                          }
   int               Width(void)                         const { return this.m_bound.Width();                                                      }
   int               Height(void)                        const { return this.m_bound.Height();                                                     }
   int               Right(void)                         const { return this.m_bound.Right();                                                      }
   int               Bottom(void)                        const { return this.m_bound.Bottom();                                                     }
   
//--- Set the new (1) X, (2) Y, (3) XY coordinate for the object
   bool              MoveX(const int x);
   bool              MoveY(const int y);
   bool              Move(const int x,const int y);
   
//--- Shift the object by (1) X, (2) Y, (3) XY xis by the specified offset

   bool              ShiftX(const int dx);
   bool              ShiftY(const int dy);
   bool              Shift(const int dx,const int dy);

//--- Return the object boundaries considering the frame
   int               LimitLeft(void)                     const { return this.X()+(int)this.m_border_width;                                         }
   int               LimitRight(void)                    const { return this.Right()-(int)this.m_border_width;                                     }
   int               LimitTop(void)                      const { return this.Y()+(int)this.m_border_width;                                         }
   int               LimitBottom(void)                   const { return this.Bottom()-(int)this.m_border_width;                                    }

//--- (1) Hide and (2) display the object on all chart periods,
//--- (3) bring the object to the front, (4) block, (5) unblock the element,
//--- (6) fill the object with the specified color with the set transparency
   virtual void      Hide(const bool chart_redraw);
   virtual void      Show(const bool chart_redraw);
   virtual void      BringToTop(const bool chart_redraw);
   virtual void      Block(const bool chart_redraw);
   virtual void      Unblock(const bool chart_redraw);
   void              Fill(const color clr,const bool chart_redraw);
   
//--- (1) Fill the object with a transparent color, (2) update the object to reflect the changes,
//--- (3) draw the appearance and (4) destroy the object
   virtual void      Clear(const bool chart_redraw);
   virtual void      Update(const bool chart_redraw);
   virtual void      Draw(const bool chart_redraw);
   virtual void      Destroy(void);
   
//--- (1) Return and (2) display the object description in the journal
   virtual string    Description(void);
   void              Print(void);
   
//--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_CANVAS_BASE); }
   
//--- Constructors/destructor
                     CCanvasBase(void) :
                        m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)), m_chart_id(::ChartID()), m_wnd(0),
                        m_alpha(0), m_hidden(false), m_blocked(false), m_focused(false), m_border_width(0), m_wnd_y(0) { }
                     CCanvasBase(const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h);
                    ~CCanvasBase(void);
  };

Eine Klasse ist eine Liste von Eigenschaften eines grafischen Elements, eine Liste von Objekten der oben genannten Klassen und eine Liste von Methoden für den Zugriff auf Variablen und Methoden von Hilfsklassen.

Als Ergebnis erhalten wir ein recht flexibles Objekt, mit dem wir die Eigenschaften und das Aussehen eines grafischen Elements steuern können. Es handelt sich um ein Objekt, das allen seinen Nachkommen grundlegende Funktionen bietet, die im Objekt selbst implementiert und in geerbten Klassen erweiterbar sind.

Betrachten wir die Methoden der Klasse.

Parametrischer Konstrukteur

//+------------------------------------------------------------------+
//| CCanvasBase::Constructor                                         |
//+------------------------------------------------------------------+
CCanvasBase::CCanvasBase(const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h) :
   m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)), m_wnd(wnd<0 ? 0 : wnd), m_alpha(0), m_hidden(false), m_blocked(false), m_focused(false), m_border_width(0)
  {
//--- Get the adjusted chart ID and the distance in pixels along the vertical Y axis
//--- between the upper frame of the indicator subwindow and the upper frame of the chart main window
   this.m_chart_id=this.CorrectChartID(chart_id);
   this.m_wnd_y=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOW_YDISTANCE,this.m_wnd);
//--- If the graphical resource and graphical object are created
   if(this.Create(this.m_chart_id,this.m_wnd,name,x,y,w,h))
     {
      //--- Clear the background and foreground canvases and set the initial coordinate values,
      //--- names of graphic objects and properties of text drawn in the foreground
      this.Clear(false);
      this.m_obj_x=x;
      this.m_obj_y=y;
      this.m_color_background.SetName("Background");
      this.m_color_foreground.SetName("Foreground");
      this.m_color_border.SetName("Border");
      this.m_foreground.FontSet("Calibri",12);
      this.m_bound.SetName("Perimeter");
     }
  }

Dem Konstruktor werden die Anfangseigenschaften des zu erstellenden Objekts übergeben, Grafikressourcen und Objekte zum Zeichnen des Hintergrunds und des Vordergrunds werden erstellt, Koordinatenwerte und Namen von Grafikobjekten werden festgelegt, Schriftparameter für die Darstellung von Texten im Vordergrund werden gesetzt.

Im Destruktor der Klasse wird das Objekt zerstört:

//+------------------------------------------------------------------+
//| CCanvasBase::Destructor                                          |
//+------------------------------------------------------------------+
CCanvasBase::~CCanvasBase(void)
  {
   this.Destroy();
  }

Eine Methode, die grafische Objekte für den Hintergrund und den Vordergrund erstellt

//+------------------------------------------------------------------+
//| CCanvasBase::Create background and foreground graphical objects  |
//+------------------------------------------------------------------+
bool CCanvasBase::Create(const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
  {
//--- Get the adjusted chart ID
   long id=this.CorrectChartID(chart_id);
//--- Create a graphical object name for the background and create a canvas
   string obj_name=name+"_BG";
   if(!this.m_background.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE))
     {
      ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name);
      return false;
     }
//--- Create a graphical object name for the foreground and create a canvas
   obj_name=name+"_FG";
   if(!this.m_foreground.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE))
     {
      ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name);
      return false;
     }
//--- If created successfully, enter the program name into the OBJPROP_TEXT property of the graphical object
   ::ObjectSetString(id,this.NameBG(),OBJPROP_TEXT,this.m_program_name);
   ::ObjectSetString(id,this.NameFG(),OBJPROP_TEXT,this.m_program_name);
   
//--- Set the dimensions of the rectangular area and return 'true'
   this.m_bound.SetXY(x,y);
   this.m_bound.Resize(w,h);
   return true;
  }

Mit den Methoden zur Erstellung von Grafikobjekten OBJ_BITMAP_LABEL der Klasse CCanvas werden Objekte zum Zeichnen des Hintergrunds und des Vordergrunds erstellt und die Koordinaten und Abmessungen des gesamten Objekts festgelegt. Es ist erwähnenswert, dass der Programmname in die Eigenschaften der erstellten OBJPROP_TEXT Grafikobjekte eingefügt wird. Auf diese Weise lässt sich feststellen, welche Grafikobjekte zu einem bestimmten Programm gehören, ohne dass dessen Name in den Namen der Grafikobjekte angegeben wird.

Eine Methode, die den Zeiger auf das übergeordnete Containerobjekt setzt

//+------------------------------------------------------------------+
//| CCanvasBase::Set the pointer                                     |
//| to the parent container object                                   |
//+------------------------------------------------------------------+
void CCanvasBase::SetContainerObj(CCanvasBase *obj)
  {
//--- Set the passed pointer to the object
   this.m_container=obj;
//--- If the pointer is empty, leave
   if(this.m_container==NULL)
      return;
//--- If an invalid pointer is passed, zero it in the object and leave
   if(::CheckPointer(this.m_container)==POINTER_INVALID)
     {
      this.m_container=NULL;
      return;
     }
//--- Clip the object along the boundaries of the container assigned to it
   this.ObjectTrim();
  }

Jedes grafische Element kann Teil seines übergeordneten Elements sein. So können beispielsweise Schaltflächen, Dropdown-Listen und andere Steuerelemente auf dem Bedienfeld angeordnet werden. Damit die untergeordneten Objekte entlang der Grenzen ihres übergeordneten Elements beschnitten werden können, muss ihnen ein Zeiger auf dieses übergeordnete Element übergeben werden.

Jedes übergeordnete Element hat Methoden, die seine Koordinaten und Begrenzungen zurückgeben. Entlang dieser Grenzen werden die untergeordneten Elemente beschnitten, wenn sie aus irgendeinem Grund die Grenzen des Containers überschreiten. Dieser Grund kann z.B. das Blättern durch den Inhalt einer großen Tabelle sein.

Eine Methode, die ein grafisches Objekt entlang der Containerkontur beschneidet

//+-----------------------------------------------------------------------+
//| CCanvasBase::Crop a graphical object to the outline of its container  |
//+-----------------------------------------------------------------------+
void CCanvasBase::ObjectTrim()
  {
//--- Get the container boundaries
   int container_left   = this.ContainerLimitLeft();
   int container_right  = this.ContainerLimitRight();
   int container_top    = this.ContainerLimitTop();
   int container_bottom = this.ContainerLimitBottom();
   
//--- Get the current object boundaries
   int object_left   = this.X();
   int object_right  = this.Right();
   int object_top    = this.Y();
   int object_bottom = this.Bottom();

//--- Check if the object is completely outside the container and hide it if it is
   if(object_right <= container_left || object_left >= container_right ||
      object_bottom <= container_top || object_top >= container_bottom)
     {
      this.Hide(true);
      this.ObjectResize(this.Width(),this.Height());
      return;
     }

//--- Check whether the object extends horizontally and vertically beyond the container boundaries
   bool modified_horizontal=false;     // Horizontal change flag
   bool modified_vertical  =false;     // Vertical change flag
   
//--- Horizontal cropping
   int new_left = object_left;
   int new_width = this.Width();
//--- If the object extends beyond the container left border
   if(object_left<=container_left)
     {
      int crop_left=container_left-object_left;
      new_left=container_left;
      new_width-=crop_left;
      modified_horizontal=true;
     }
//--- If the object extends beyond the container right border
   if(object_right>=container_right)
     {
      int crop_right=object_right-container_right;
      new_width-=crop_right;
      modified_horizontal=true;
     }
//--- If there were changes horizontally
   if(modified_horizontal)
     {
      this.ObjectSetX(new_left);
      this.ObjectResizeW(new_width);
     }

//--- Vertical cropping
   int new_top=object_top;
   int new_height=this.Height();
//--- If the object extends beyond the top edge of the container
   if(object_top<=container_top)
     {
      int crop_top=container_top-object_top;
      new_top=container_top;
      new_height-=crop_top;
      modified_vertical=true;
     }
//--- If the object extends beyond the bottom border of the container
   if(object_bottom>=container_bottom)
     {
      int crop_bottom=object_bottom-container_bottom;
      new_height-=crop_bottom;
      modified_vertical=true;
     }
//--- If there were vertical changes
   if(modified_vertical)
     {
      this.ObjectSetY(new_top);
      this.ObjectResizeH(new_height);
     }

//--- After calculations, the object may be hidden, but is now in the container area - display it
   this.Show(false);

//--- If the object has been changed, redraw it
   if(modified_horizontal || modified_vertical)
     {
      this.Update(false);
      this.Draw(false);
     }
  }

Dies ist eine virtuelle Methode, d.h. sie kann in abgeleiteten Klassen neu definiert werden. Die Logik der Methode wird in den Kommentaren zum Code ausführlich erläutert. Bei jedem grafischen Element und einigen seiner Transformationen (Verschieben, Größenänderung usw.) wird immer geprüft, ob es über seinen Container hinausgeht. Wenn ein Objekt keinen Container hat, wird es nicht getrimmt.

Eine Methode, die die X-Koordinate eines grafischen Objekts festlegt

//+------------------------------------------------------------------+
//| CCanvasBase::Set the X coordinate of the graphical object        |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectSetX(const int x)
  {
//--- If an existing coordinate is passed, return 'true'
   if(this.ObjectX()==x)
      return true;
//--- If failed to set a new coordinate in the background and foreground graphical objects, return 'false'
   if(!::ObjectSetInteger(this.m_chart_id,this.NameBG(),OBJPROP_XDISTANCE,x) || !::ObjectSetInteger(this.m_chart_id,this.NameFG(),OBJPROP_XDISTANCE,x))
      return false;
//--- Set the new coordinate to the variable and return 'true'
   this.m_obj_x=x;
   return true;
  }

Nur wenn die Koordinaten erfolgreich auf zwei grafische Objekte – die Hintergrundleinwand und die Vordergrundleinwand – gesetzt werden, gibt die Methode true zurück .

Eine Methode, die die Y-Koordinate eines grafischen Objekts festlegt

//+------------------------------------------------------------------+
//| CCanvasBase::Set the Y coordinate of the graphical object        |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectSetY(const int y)
  {
//--- If an existing coordinate is passed, return 'true'
   if(this.ObjectY()==y)
      return true;
//--- If failed to set a new coordinate in the background and foreground graphical objects, return 'false'
   if(!::ObjectSetInteger(this.m_chart_id,this.NameBG(),OBJPROP_YDISTANCE,y) || !::ObjectSetInteger(this.m_chart_id,this.NameFG(),OBJPROP_YDISTANCE,y))
      return false;
//--- Set the new coordinate to the variable and return 'true'
   this.m_obj_y=y;
   return true;
  }

Die Methode ist identisch mit der oben beschriebenen.

Eine Methode, die die Breite eines grafischen Objekts ändert

//+------------------------------------------------------------------+
//| CCanvasBase::Change the graphical object width                   |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectResizeW(const int size)
  {
//--- If an existing width is passed, return 'true'
   if(this.ObjectWidth()==size)
      return true;
//--- If the passed size is greater than 0, return the result of changing the background and foreground widths, otherwise - 'false'
   return(size>0 ? (this.m_background.Resize(size,this.ObjectHeight()) && this.m_foreground.Resize(size,this.ObjectHeight())) : false);
  }

Nur die Breite mit einem Wert größer als Null wird zur Bearbeitung akzeptiert. Die Methode gibt nur dann true zurück, wenn die Breite der Hintergrund- und Vordergrundleinwand erfolgreich geändert wurde.

Eine Methode, die die Höhe eines grafischen Objekts ändert

//+------------------------------------------------------------------+
//| CCanvasBase::Change the graphical object height                  |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectResizeH(const int size)
  {
//--- If an existing height is passed, return 'true'
   if(this.ObjectHeight()==size)
      return true;
//--- If the passed size is greater than 0, return the result of changing the background and foreground heights, otherwise - 'false'
   return(size>0 ? (this.m_background.Resize(this.ObjectWidth(),size) && this.m_foreground.Resize(this.ObjectWidth(),size)) : false);
  }

Die Methode ist identisch mit der oben beschriebenen.

Eine Methode zur Größenänderung eines grafischen Objekts

//+------------------------------------------------------------------+
//| CCanvasBase::Change the graphical object size                    |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectResize(const int w,const int h)
  {
   if(!this.ObjectResizeW(w))
      return false;
   return this.ObjectResizeH(h);
  }

Hier werden die Methoden zur Änderung der Breite und Höhe abwechselnd aufgerufen. Sie gibt nur dann true zurück, wenn sowohl Breite als auch Höhe erfolgreich geändert wurden.

Eine Methode, die neue X- und Y-Koordinaten für ein Objekt festlegt

//+------------------------------------------------------------------+
//| CCanvasBase::Set new X and Y object coordinates                  |
//+------------------------------------------------------------------+
bool CCanvasBase::Move(const int x,const int y)
  {
   if(!this.ObjectMove(x,y))
      return false;
   this.BoundMove(x,y);
   this.ObjectTrim();
   return true;
  }

Zunächst werden die grafischen Objekte des Hintergrunds und des Vordergrunds zu den angegebenen Koordinaten verschoben. Wenn die neuen Koordinaten für die grafischen Objekte erfolgreich gesetzt wurden, werden die gleichen Koordinaten auch für das Objekt selbst gesetzt. Danach wird geprüft, ob das Objekt die Grenzen des Containers nicht überschritten hat, und es wird true zurückgegeben.

Eine Methode, die eine neue X-Koordinate für ein Objekt festlegt

//+------------------------------------------------------------------+
//| CCanvasBase::Set the object new X coordinate                     |
//+------------------------------------------------------------------+
bool CCanvasBase::MoveX(const int x)
  {
   return this.Move(x,this.ObjectY());
  }

Eine Hilfsmethode, die nur die horizontale Koordinate festlegt. Die vertikale Koordinate bleibt aktuell.

Eine Methode, die eine neue Y-Koordinate für ein Objekt festlegt

//+------------------------------------------------------------------+
//| CCanvasBase::Set the object new Y coordinate                     |
//+------------------------------------------------------------------+
bool CCanvasBase::MoveY(const int y)
  {
   return this.Move(this.ObjectX(),y);
  }

Eine Hilfsmethode, die nur die vertikale Koordinate festlegt. Die horizontale Koordinate bleibt aktuell.

Eine Methode, die ein Objekt entlang der X- und Y-Achse um einen bestimmten Offset verschiebt

//+--------------------------------------------------------------------------------+
//| CCanvasBase::Offset the object along the X and Y axes by the specified offset  |
//+--------------------------------------------------------------------------------+
bool CCanvasBase::Shift(const int dx,const int dy)
  {
   if(!this.ObjectShift(dx,dy))
      return false;
   this.BoundShift(dx,dy);
   this.ObjectTrim();
   return true;
  }

Im Gegensatz zur Methode Move, die die Bildschirmkoordinaten für ein Objekt festlegt, gibt die Methode Shift einen lokalen Versatz um die Anzahl der Pixel relativ zu den Bildschirmkoordinaten des Objekts an. Zunächst werden die grafischen Objekte des Hintergrunds und des Vordergrunds verschoben. Und dann werden die gleichen Offsets auf das Objekt selbst gesetzt. Anschließend wird geprüft, ob das Objekt die Grenzen des Containers überschreitet, und true zurückgegeben.

Eine Methode, die ein Objekt entlang der X-Achse um einen bestimmten Offset verschiebt

//+-------------------------------------------------------------------------------+
//| CCanvasBase::Offset the object along the X axis by the specified offset       |
//+-------------------------------------------------------------------------------+
bool CCanvasBase::ShiftX(const int dx)
  {
   return this.Shift(dx,0);
  }

Eine Hilfsmethode, die das Objekt nur in horizontaler Richtung bewegt. Die vertikale Koordinate bleibt aktuell.

Eine Methode, die ein Objekt entlang der Y-Achse um einen bestimmten Offset verschiebt

//+-------------------------------------------------------------------------------+
//| CCanvasBase::Offset the object along the Y axis by the specified offset       |
//+-------------------------------------------------------------------------------+
bool CCanvasBase::ShiftY(const int dy)
  {
   return this.Shift(0,dy);
  }

Eine Hilfsmethode, die das Objekt nur vertikal bewegt. Die horizontale Koordinate bleibt aktuell.

Eine Methode, die ein Objekt in allen Chart-Perioden ausblendet

//+------------------------------------------------------------------+
//| CCanvasBase::Hide the object on all chart periods                |
//+------------------------------------------------------------------+
void CCanvasBase::Hide(const bool chart_redraw)
  {
//--- If the object is already hidden, leave
   if(this.m_hidden)
      return;
//--- If the visibility change for background and foreground is successfully set
//--- in the chart command queue - set the hidden object flag
   if(::ObjectSetInteger(this.m_chart_id,this.NameBG(),OBJPROP_TIMEFRAMES,OBJ_NO_PERIODS) &&
      ::ObjectSetInteger(this.m_chart_id,this.NameFG(),OBJPROP_TIMEFRAMES,OBJ_NO_PERIODS)
      ) this.m_hidden=true;
//--- If specified, redraw the chart
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Um ein grafisches Objekt im Chart auszublenden, setzen wir den Wert OBJ_NO_PERIODS für dessen Eigenschaft OBJPROP_TIMEFRAMES. Wenn die Eigenschaft für das Hintergrundobjekt und das Vordergrundobjekt (in der Warteschlange für Chart-Ereignisse) erfolgreich gesetzt wurde, setzen wir das Flag für das ausgeblendete Objekt und zeichnen das Chart neu, falls angegeben.

Eine Methode, die ein Objekt in allen Chart-Perioden anzeigt

//+------------------------------------------------------------------+
//| CCanvasBase::Display an object on all chart periods              |
//+------------------------------------------------------------------+
void CCanvasBase::Show(const bool chart_redraw)
  {
//--- If the object is already visible, leave
   if(!this.m_hidden)
      return;
//--- If the visibility change for background and foreground is successfully set
//--- in the chart command queue - reset the hidden object flag
   if(::ObjectSetInteger(this.m_chart_id,this.NameBG(),OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS) &&
      ::ObjectSetInteger(this.m_chart_id,this.NameFG(),OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS)
      ) this.m_hidden=false;
//--- If specified, redraw the chart
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Um ein grafisches Objekt im Chart anzuzeigen, setzen wir den Wert OBJ_ALL_PERIODS für dessen Eigenschaft OBJPROP_TIMEFRAMES. Wenn die Eigenschaft für das Hintergrundobjekt und das Vordergrundobjekt (in der Warteschlange für Chart-Ereignisse) erfolgreich gesetzt wurde, wird das Flag für das ausgeblendete Objekt entfernt und das Chart, falls angegeben, neu gezeichnet.

Eine Methode, die ein Objekt in den Vordergrund stellt

//+------------------------------------------------------------------+
//| CCanvasBase::Bring an object to the foreground                   |
//+------------------------------------------------------------------+
void CCanvasBase::BringToTop(const bool chart_redraw)
  {
   this.Hide(false);
   this.Show(chart_redraw);
  }

Um ein grafisches Objekt im Chart vor allen anderen zu platzieren, muss es nacheinander ausgeblendet und sofort wieder eingeblendet werden, was mit dieser Methode geschieht.

Methoden für das Farbmanagement eines Objekts in seinen verschiedenen Zuständen

//+------------------------------------------------------------------+
//| CCanvasBase::Set the current element colors                      |
//| to default state                                                 |
//+------------------------------------------------------------------+
bool CCanvasBase::ColorsToDefault(void)
  {
   bool res=true;
   res &=this.BackColorToDefault();
   res &=this.ForeColorToDefault();
   res &=this.BorderColorToDefault();
   return res;
  }
//+------------------------------------------------------------------+
//| CCanvasBase::Set the current element colors                      |
//| to on-hover state                                                |
//+------------------------------------------------------------------+
bool CCanvasBase::ColorsToFocused(void)
  {
   bool res=true;
   res &=this.BackColorToFocused();
   res &=this.ForeColorToFocused();
   res &=this.BorderColorToFocused();
   return res;
  }
//+------------------------------------------------------------------+
//| CCanvasBase::Set the current element colors                      |
//| to on-click state                                                |
//+------------------------------------------------------------------+
bool CCanvasBase::ColorsToPressed(void)
  {
   bool res=true;
   res &=this.BackColorToPressed();
   res &=this.ForeColorToPressed();
   res &=this.BorderColorToPressed();
   return res;
  }
//+------------------------------------------------------------------+
//| CCanvasBase::Set the current element colors                      |
//| to blocked state                                                 |
//+------------------------------------------------------------------+
bool CCanvasBase::ColorsToBlocked(void)
  {
   bool res=true;
   res &=this.BackColorToBlocked();
   res &=this.ForeColorToBlocked();
   res &=this.BorderColorToBlocked();
   return res;
  }

Das grafische Element besteht aus drei Teilen, deren Farben separat festgelegt werden:

  • Hintergrundfarbe, 
  • Textfarbe,
  • Randfarbe.

Diese drei Elemente können ihre Farbe je nach Zustand des Elements ändern. Solche Zustände können sein:

  • einen normalen Zustand des Grafikelements,
  • wenn der Mauszeiger über dem Element schwebt (Fokus),
  • beim Klicken auf das Element (Klick),
  • Element blockiert ist.

Die Farbe jedes einzelnen Elements (Hintergrund, Text, Rahmen) kann separat eingestellt und angepasst werden. Aber normalerweise ändern diese drei Komponenten ihre Farben synchron, je nach Zustand des Elements bei der Interaktion mit dem Nutzer.

Mit den oben beschriebenen Methoden können wir die Farben eines Objekts für seine verschiedenen Zustände für drei Elemente gleichzeitig festlegen.

Eine Methode, die das Element blockiert

//+------------------------------------------------------------------+
//| CCanvasBase::Block the element                                   |
//+------------------------------------------------------------------+
void CCanvasBase::Block(const bool chart_redraw)
  {
//--- If the element has already been blocked, leave
   if(this.m_blocked)
      return;
//--- Set the current colors as the colors of the blocked element, 
//--- redraw the object and set the block flag
   this.ColorsToBlocked();
   this.Draw(chart_redraw);
   this.m_blocked=true;
  }

Wenn ein Element gesperrt wird, werden die Farben des gesperrten Zustands für es festgelegt, das Objekt wird neu gezeichnet, um neue Farben anzuzeigen, und das Sperrkennzeichen wird gesetzt.

Eine Methode, die die Blockierung des Elements aufhebt

//+------------------------------------------------------------------+
//| CCanvasBase::Unblock the element                                 |
//+------------------------------------------------------------------+
void CCanvasBase::Unblock(const bool chart_redraw)
  {
//--- If the element has already been unblocked, leave
   if(!this.m_blocked)
      return;
//--- Set the current colors as the colors of the element in its normal state, 
//--- redraw the object and reset the block flag
   this.ColorsToDefault();
   this.Draw(chart_redraw);
   this.m_blocked=false;
  }

Wenn ein Element entsperrt wird, werden die Farben des Normalzustands für es gesetzt, das Objekt wird neu gezeichnet, um die neuen Farben anzuzeigen, und das Sperrkennzeichen wird entfernt.

Eine Methode, die ein Objekt mit der angegebenen Farbe füllt

//+------------------------------------------------------------------+
//| CCanvasBase::Fill an object with the specified color             |
//| with transparency set to                                         |
//+------------------------------------------------------------------+
void CCanvasBase::Fill(const color clr,const bool chart_redraw)
  {
   this.m_background.Erase(::ColorToARGB(clr,this.m_alpha));
   this.m_background.Update(chart_redraw);
  }

Manchmal ist es notwendig, den gesamten Hintergrund eines Objekts vollständig mit einer Farbe auszufüllen. Diese Methode füllt den Objekthintergrund mit einer Farbe aus, ohne den Vordergrund zu beeinflussen. Zum Ausfüllen wird die Transparenz verwendet, die auf die Klassenvariable m_alpha voreingestellt ist. Außerdem wird der Hintergrund aktualisiert, um die Änderungen mit dem Flag für das Neuzeichnen des Charts zu fixieren.

Wenn das Flag gesetzt ist, werden die Änderungen sofort nach der Aktualisierung der Leinwand angezeigt. Wird das Chart-Aktualisierungsflag zurückgesetzt, wird das Erscheinungsbild des Objekts entweder mit einem neuen Tick oder mit dem nächsten Aufruf des Chart-Aktualisierungsbefehls aktualisiert. Dies ist notwendig, wenn mehrere Objekte gleichzeitig neu gestrichen werden. Das Redraw-Flag sollte nur für das letzte neu zu zeichnende Objekt gesetzt werden.

Diese Logik gilt im Allgemeinen für alle Fälle, in denen grafische Objekte geändert werden – entweder handelt es sich um ein einzelnes Element, das sofort nach der Änderung aktualisiert wird, oder es handelt sich um eine Stapelverarbeitung mehrerer Elemente, bei der die Grafik erst nach der Änderung des letzten grafischen Elements neu gezeichnet werden muss.

Eine Methode, die ein Objekt mit einer transparenten Farbe füllt

//+------------------------------------------------------------------+
//| CCanvasBase::Fill an object with transparent color               |
//+------------------------------------------------------------------+
void CCanvasBase::Clear(const bool chart_redraw)
  {
   this.m_background.Erase(clrNULL);
   this.m_foreground.Erase(clrNULL);
   this.Update(chart_redraw);
  }

Die Hintergrund-Leinwand und die Vordergrund-Leinwand werden mit einer transparenten Farbe gefüllt, und beide Objekte werden aktualisiert, indem das Flag zum erneuten Zeichnen der Grafik gesetzt wird.

Eine Methode, die ein Objekt aktualisiert, um Änderungen anzuzeigen

//+------------------------------------------------------------------+
//| CCanvasBase::Update the object to display the changes            |
//+------------------------------------------------------------------+
void CCanvasBase::Update(const bool chart_redraw)
  {
   this.m_background.Update(false);
   this.m_foreground.Update(chart_redraw);
  }

Die Hintergrund-Leinwand wird aktualisiert, ohne dass der Graph neu gezeichnet wird, und bei der Aktualisierung der Vordergrund-Leinwand wird der angegebene Wert des Graph-Redraw-Flags verwendet. Dadurch können beide CCanvas-Objekte gleichzeitig aktualisiert werden, während das Neuzeichnen des Charts für mehrere Objekte durch Angabe des Redraw-Flags für die Methode gesteuert wird.

Eine Methode, die das Aussehen zeichnet

//+------------------------------------------------------------------+
//| CCanvasBase::Draw the appearance                                 |
//+------------------------------------------------------------------+
void CCanvasBase::Draw(const bool chart_redraw)
  {
   return;
  }

Dies ist eine virtuelle Methode. Seine Implementierung muss in geerbten Klassen erfolgen. Diese Methode tut hier nichts, da für das Basisobjekt kein Rendering auf dem Graphen stattfinden muss – es ist lediglich ein Basisobjekt, auf dem Basissteuerungen implementiert werden.

Eine Methode, die das Objekt vernichtet

//+------------------------------------------------------------------+
//| CCanvasBase::Destroy the object                                  |
//+------------------------------------------------------------------+
void CCanvasBase::Destroy(void)
  {
   this.m_background.Destroy();
   this.m_foreground.Destroy();
  }

Beide Leinwände werden mit den Methoden Destroy der CCanvas-Klasse zerstört.

Eine Methode, die die Beschreibung eines Objekts zurückgibt

//+------------------------------------------------------------------+
//| CCanvasBase::Return the object description                       |
//+------------------------------------------------------------------+
string CCanvasBase::Description(void)
  {
   string nm=this.Name();
   string name=(nm!="" ? ::StringFormat(" \"%s\"",nm) : nm);
   string area=::StringFormat("x %d, y %d, w %d, h %d",this.X(),this.Y(),this.Width(),this.Height());
   return ::StringFormat("%s%s (%s, %s): ID %d, %s",ElementDescription((ENUM_ELEMENT_TYPE)this.Type()),name,this.NameBG(),this.NameFG(),this.ID(),area);
  }

Sie gibt die Objektbeschreibung mit der Beschreibung, dem Bezeichner, den Namen der grafischen Objekte des Hintergrunds und des Vordergrunds zurück und gibt die Koordinaten und Abmessungen des Objekts im folgenden Format an:

Canvas Base "Rectangle 1" (TestScr1_BG, TestScr1_FG): ID 1, x 100, y 40, w 100, h 100
Canvas Base "Rectangle 2" (TestScr2_BG, TestScr2_FG): ID 2, x 110, y 50, w 80, h 80

Eine Methode, die eine Objektbeschreibung in das Protokoll ausgibt

//+------------------------------------------------------------------+
//| CCanvasBase::Display the object description in the journal       |
//+------------------------------------------------------------------+
void CCanvasBase::Print(void)
  {
   ::Print(this.Description());
  }

Gibt die Objektbeschreibung, die von der Methode Description zurückgegeben wird, im Protokoll aus.

Die Methoden zum Speichern eines grafischen Elements in einer Datei und zum Hochladen aus einer Datei sind noch nicht implementiert – es wurde lediglich ein Rohling für sie erstellt:

//+------------------------------------------------------------------+
//| CCanvasBase::Save to file                                        |
//+------------------------------------------------------------------+
bool CCanvasBase::Save(const int file_handle)
  {
//--- Method temporarily disabled
   return false;
   
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Save data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,-1)!=sizeof(long))
      return false;
//--- Save the object type
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return false;

/*
//--- Store the properties
      
*/
//--- All is successful
   return true;
  }
//+------------------------------------------------------------------+
//| Load from file                                                   |
//+------------------------------------------------------------------+
bool CCanvasBase::Load(const int file_handle)
  {
//--- Method temporarily disabled
   return false;
   
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=-1)
      return false;
//--- Load the object type
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return false;

/*
//--- Load properties
   
*/
//--- All is successful
   return true;
  }

Da es sich um die erste Version des Basisobjekts handelt, die wahrscheinlich noch weiterentwickelt wird, sind die Methoden für die Arbeit mit Dateien noch nicht implementiert worden. Wenn wir diese Klasse verfeinern, neue Eigenschaften hinzufügen und die vorhandenen optimieren, müssen wir gleichzeitig Änderungen an den Methoden für die Arbeit mit Dateien vornehmen. Um unnötige Arbeit zu vermeiden, sollten wir mit der Implementierung der Methoden Speichern und Laden warten, bis die Arbeit am Basisobjekt der grafischen Elemente vollständig abgeschlossen ist.

Wir sind jetzt bereit für Tests.



Testen des Ergebnisses

Um zu testen, wie die Klasse läuft, erstellen wir zwei übereinander liegende Objekte. Das erste Objekt wird ein Container für das zweite sein. Und das zweite Objekt wird programmatisch innerhalb des übergeordneten Elements in alle möglichen Richtungen verschoben. Auf diese Weise lernen wir die korrekte Funktionsweise der Methoden zum Verschieben, zur Größenänderung von Elementen und zum Anpassen des untergeordneten Elements an die Größe des Containers kennen. Bevor wir die Arbeit abschließen, setzen wir das Flag des gesperrten Elements auf das zweite Objekt, um zu prüfen, wie es funktioniert.

Aber es gibt trotzdem ein „aber“. Die Methode Draw tut nichts im Basisobjekt, und wir werden die Klassenoperation einfach nicht sehen, da die entwickelten Objekte völlig transparent sein werden.

Füllen wir einfach das erste Objekt mit Farbe und zeichnen wir einen Rahmen. Da sie sich nicht bewegt und ihre Größe nicht ändert, müssen wir sie auch nicht neu zeichnen. Es reicht aus, wenn wir nach der Erstellung des Objekts einmal etwas darauf zeichnen. Gleichzeitig muss das Aussehen des zweiten Objekts ständig aktualisiert werden, da die Methode ObjectTrim() die Methode zum Neuzeichnen des Objekts aufruft. Aber in dieser Klasse tut diese Methode nichts. Ändern wir also vorübergehend die Draw-Methode, damit etwas auf das Objekt gezeichnet wird:

//+------------------------------------------------------------------+
//| CCanvasBase::Draw the appearance                                 |
//+------------------------------------------------------------------+
void CCanvasBase::Draw(const bool chart_redraw)
  {
   //return;
   Fill(BackColor(),false);
   m_background.Rectangle(this.AdjX(0),this.AdjY(0),AdjX(this.Width()-1),AdjY(this.Height()-1),ColorToARGB(this.BorderColor()));
   
   m_foreground.Erase(clrNULL);
   m_foreground.TextOut(AdjX(6),AdjY(6),StringFormat("%dx%d (%dx%d)",this.Width(),this.Height(),this.ObjectWidth(),this.ObjectHeight()),ColorToARGB(this.ForeColor()));
   m_foreground.TextOut(AdjX(6),AdjY(16),StringFormat("%dx%d (%dx%d)",this.X(),this.Y(),this.ObjectX(),this.ObjectY()),ColorToARGB(this.ForeColor()));
   
   Update(chart_redraw);
  }

Füllen wir hier für die Hintergrundleinwand den Hintergrund mit der eingestellten Hintergrundfarbe und zeichnen den Rahmen mit der eingestellten Rahmenfarbe.

Leeren wir die Leinwand im Vordergrund, und zeigen wir zwei Texte in der eingestellten Textfarbe untereinander an:

  1. mit der Breite/Höhe des Objekts und in Klammern mit der Breite/Höhe der grafischen Objekte des Hintergrunds und des Vordergrunds;
  2. mit den X/Y-Koordinaten des Objekts und in Klammern mit den X/Y-Koordinaten der grafischen Objekte des Hintergrunds und des Vordergrunds.

Nach dem Testen entfernen wir diesen Code aus der Methode.

Erstellen wir im Ordner \MQL5\Scripts\Tables\ die Testskriptdatei TestControls.mq5:

//+------------------------------------------------------------------+
//|                                                 TestControls.mq5 |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Include libraries                                                |
//+------------------------------------------------------------------+
#include "Controls\Base.mqh"
  
CCanvasBase *obj1=NULL;       // Pointer to the first graphical element
CCanvasBase *obj2=NULL;       // Pointer to the second graphical element
  
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Create the first graphical element
   obj1=new CCanvasBase(0,0,"TestScr1",100,40,160,160);
   obj1.SetAlpha(250);        // Transparency
   obj1.SetBorderWidth(6);    // Frame width
//--- Fill the background with color and draw a frame with an indent of one pixel from the set frame width
   obj1.Fill(clrDodgerBlue,false);
   uint wd=obj1.BorderWidth();
   obj1.GetBackground().Rectangle(wd-2,wd-2,obj1.Width()-wd+1,obj1.Height()-wd+1,ColorToARGB(clrWheat));
   obj1.Update(false);
//--- set the name and ID of the element and display its description in the journal
   obj1.SetName("Rectangle 1");
   obj1.SetID(1);
   obj1.Print();

//--- Create a second element inside the first one, set its transparency
//--- and specify the first element as a container for the second one
   int shift=10;
   int x=obj1.X()+shift;
   int y=obj1.Y()+shift;
   int w=obj1.Width()-shift*2;
   int h=obj1.Height()-shift*2;
   obj2=new CCanvasBase(0,0,"TestScr2",x,y,w,h);
   obj2.SetAlpha(250);
   obj2.SetContainerObj(obj1);

//--- Initialize the background color, specify the color for the blocked element
//--- and set the default background color of the element as the current color 
   obj2.InitBackColors(clrLime);
   obj2.InitBackColorBlocked(clrLightGray);
   obj2.BackColorToDefault();

//--- Initialize the foreground color, specify the color for the blocked element
//--- and set the default text color of the element as the current foreground color 
   obj2.InitForeColors(clrBlack);
   obj2.InitForeColorBlocked(clrDimGray);
   obj2.ForeColorToDefault();

//--- Initialize the frame color, specify the color for the blocked element
//--- and set the default frame color of the element as the current color 
   obj2.InitBorderColors(clrBlue);
   obj2.InitBorderColorBlocked(clrSilver);
   obj2.BorderColorToDefault();
//--- Set the element name and ID,
//--- display its description in the journal and draw the element
   obj2.SetName("Rectangle 2");
   obj2.SetID(2);
   obj2.Print();
   obj2.Draw(true);
   
//--- Check if the element is clipped by its container boundaries
   int ms=1;         // Offset delay in milliseconds
   int total=obj1.Width()-shift; // Number of offset loop iterations
   
//--- Wait a second and move the inner object beyond the left edge of the container
   Sleep(1000);
   ShiftHorisontal(-1,total,ms);
//--- Wait a second and return the internal object to its original location
   Sleep(1000);
   ShiftHorisontal(1,total,ms);

//--- Wait a second and move the inner object beyond the right edge of the container
   Sleep(1000);
   ShiftHorisontal(1,total,ms);
//--- Wait a second and return the internal object to its original location
   Sleep(1000);
   ShiftHorisontal(-1,total,ms);

   
//--- Wait a second and move the inner object beyond the top edge of the container
   Sleep(1000);
   ShiftVertical(-1,total,ms);
//--- Wait a second and return the internal object to its original location
   Sleep(1000);
   ShiftVertical(1,total,ms);
     
//--- Wait a second and move the inner object beyond the bottom edge of the container
   Sleep(1000);
   ShiftVertical(1,total,ms);
//--- Wait a second and return the internal object to its original location
   Sleep(1000);
   ShiftVertical(-1,total,ms);

//--- Wait a second and set the blocked element flag for the inside object
   Sleep(1000);
   obj2.Block(true);

//--- Clean up in three seconds before finishing work
   Sleep(3000);
   delete obj1;
   delete obj2;
  }
//+------------------------------------------------------------------+
//| Shift the object horizontally                                    |
//+------------------------------------------------------------------+
void ShiftHorisontal(const int dx, const int total, const int delay)
  {
   for(int i=0;i<total;i++)
     {
      if(obj2.ShiftX(dx))
         ChartRedraw();
      Sleep(delay);
     }
  }
//+------------------------------------------------------------------+
//| Shift the object vertically                                      |
//+------------------------------------------------------------------+
void ShiftVertical(const int dy, const int total, const int delay)
  {
   for(int i=0;i<total;i++)
     {
      if(obj2.ShiftY(dy))
         ChartRedraw();
      Sleep(delay);
     }
  }
//+------------------------------------------------------------------+

Der Skriptcode ist ausführlich kommentiert. Die gesamte Logik ist anhand der Kommentare leicht zu verstehen.

Kompilieren wir das Skript und führen wir es im Chart aus:

Das untergeordnete Objekt wird korrekt entlang der Ränder des Containerbereichs beschnitten (nicht entlang der Kanten des Objekts, sondern mit einem Versatz um die Breite des Randes), das Blockieren des Objekts bewirkt, dass es in den Farben des blockierten Elements neu gezeichnet wird.

Etwas „Ruckeln“ beim Verschieben eines im Container verschachtelten Objekts sind auf Fehler bei der Aufnahme eines GIF-Bildes zurückzuführen, nicht auf Verzögerungen bei der Ausführung von Klassenmethoden.

Nach der Ausführung des Skripts werden zwei Zeilen mit der Beschreibung der beiden erstellten Objekte in das Protokoll geschrieben:

Canvas Base "Rectangle 1" (TestScr1_BG, TestScr1_FG): ID 1, x 100, y 40, w 160, h 160
Canvas Base "Rectangle 2" (TestScr2_BG, TestScr2_FG): ID 2, x 110, y 50, w 140, h 140


Schlussfolgerung

Heute haben wir die Grundlage für die Erstellung beliebiger grafischer Elemente geschaffen, bei denen jede Klasse eine klar definierte Aufgabe erfüllt, was die Architektur modular und leicht erweiterbar macht.

Implementierte Klassen bilden eine solide Grundlage für die Erstellung komplexer grafischer Elemente und deren Integration in Modell- und Controller-Komponenten im MVC-Paradigma.

Im nächsten Artikel werden wir mit der Erstellung aller notwendigen Elemente für die Erstellung und Verwaltung von Tabellen beginnen. Da in der MQL-Sprache das Ereignismodell mit Hilfe von Chart-Ereignissen in die erstellten Objekte integriert ist, wird die Ereignisbehandlung in allen nachfolgenden Steuerelementen organisiert, um die Verbindung zwischen der View-Komponente und der Controller-Komponente zu implementieren.

Die Programme dieses Artikels:

#
 Name Typ
Beschreibung
 1  Base.mqh  Klassenbibliothek  Klassen zur Erstellung eines Basisobjekts von Steuerelementen
 2  TestControls.mq5  Test-Skript  Skript zum Testen von Manipulationen mit der Basisobjektklasse
 3  MQL5.zip  Archive  Ein Archiv mit den oben genannten Dateien zum Entpacken in das MQL5-Verzeichnis des Client-Terminals

Klassen innerhalb der Base.mqh-Bibliothek:

#
Name
 Beschreibung
 1  CBaseObj  Eine Basisklasse für alle grafischen Objekte
 2  CColor  Farbmanagement-Klasse
 3  CColorElement  Eine Klasse zur Verwaltung der Farben der verschiedenen Zustände eines grafischen Elements
 4  CBound  Kontrollklasse des Rechtecks
 5  CCanvasBase  Eine Basisklasse für die Bearbeitung von grafischen Elementen auf der Leinwand
Alle erstellten Dateien sind dem Artikel zum Selbststudium beigefügt. Die Archivdatei kann in den Terminal-Ordner entpackt werden, und alle Dateien befinden sich dann im gewünschten Ordner: MQL5\Scripts\Tables.

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

Beigefügte Dateien |
Base.mqh (139.67 KB)
TestControls.mq5 (10.86 KB)
MQL5.zip (15.56 KB)
Entwicklung eines Expertenberaters für mehrere Währungen (Teil 24): Hinzufügen einer neuen Strategie (II) Entwicklung eines Expertenberaters für mehrere Währungen (Teil 24): Hinzufügen einer neuen Strategie (II)
In diesem Artikel werden wir die neue Strategie mit dem erstellten automatischen Optimierungssystem verbinden. Schauen wir uns an, welche Änderungen am EA für die Erstellung des Optimierungsprojekts sowie an den EAs der zweiten und dritten Stufe vorgenommen werden müssen.
Vom Neuling zum Experten: Automatisierung der Handelsdisziplin mit einem MQL5 Risk Enforcement EA Vom Neuling zum Experten: Automatisierung der Handelsdisziplin mit einem MQL5 Risk Enforcement EA
Für viele Händler ist die Lücke zwischen der Kenntnis einer Risikoregel und deren konsequenter Befolgung der Punkt, an dem die Konten sterben. Emotionale Übertreibungen, Kompensationshandel und einfaches Versehen können selbst die beste Strategie zunichte machen. Heute werden wir die MetaTrader 5-Plattform in einen unnachgiebigen Vollstrecker Ihrer Handelsregeln verwandeln, indem wir einen Risk Enforcement Expert Advisor entwickeln. Nehmen Sie an dieser Diskussion teil und erfahren Sie mehr.
Die View- und Controller-Komponenten für Tabellen im MQL5 MVC-Paradigma: Einfache Steuerung Die View- und Controller-Komponenten für Tabellen im MQL5 MVC-Paradigma: Einfache Steuerung
Der Artikel behandelt einfache Steuerelemente als Komponenten von komplexeren grafischen Elementen der View-Komponente im Rahmen der Tabellenimplementierung im MVC-Paradigma (Model-View-Controller). Die Grundfunktionalität des Controllers ist für die Interaktion der Elemente mit dem Nutzer und untereinander implementiert. Dies ist der zweite Artikel über die Komponente View und der vierte in einer Reihe von Artikeln über die Erstellung von Tabellen für das MetaTrader 5 Client Terminal.
Tabellen- und Kopfzeilen-Klassen auf der Grundlage eines Tabellenmodells in MQL5: Anwendung des MVC-Konzepts Tabellen- und Kopfzeilen-Klassen auf der Grundlage eines Tabellenmodells in MQL5: Anwendung des MVC-Konzepts
Dies ist der zweite Teil des Artikels, der sich mit der Implementierung des Tabellenmodells in MQL5 unter Verwendung des MVC (Model-View-Controller) Architekturparadigmas beschäftigt. Der Artikel behandelt die Entwicklung von Tabellenklassen und des Tabellenkopfes auf der Grundlage eines zuvor erstellten Tabellenmodells. Die entwickelten Klassen bilden die Grundlage für die weitere Implementierung von View- und Controller-Komponenten, die in den folgenden Artikeln behandelt werden.