Design und Implementierung neuer grafischer Benutzerschnittstellen-Widgets auf Grundlage der CChartObject Klasse

investeo | 14 März, 2016

Einleitung

Nachdem ich den vorigen Beitrag über einen halb-automatischen Expert Advisor mit grafischer Benutzerschnittstelle verfasst hatte, hat sich herausgestellt, dass es durchaus wünschenswert ist, diese Schnittstelle noch mit einigen neuen Funktionalitäten für komplexere Indikatoren und Expert Advisors aufzupeppen. Nachdem ich mich mit den MQL5 Standard Library-Klassen vertraut gemacht hatte, habe ich neue Widgets implementiert.

Ich beschreibe also hier wie man MQL5 Standard Library Klassen für grafische Benutzerschnittstellen-Objekte verwendet und wie man neue, aus der CChartObjectEdit Klasse, abgeleitete Klassen implementiert, und zwar: CChartObjectProgressBar, CChartObjectSpinner und CChartEditTable. Die CChartEditTable Klasse arbeitet mit einem dynamischen, zweidimensionalen Array an Objekten. Dies ist ein Arbeitsbeispiel wie man dynamische 2D-Objekt-Arrays in MQL5 implementiert.

 

1. CChartObject und seine Nachkommen

Wenn wir nicht die Standard MQL5 Library-Klasse verwenden, müssen wir mit Object Functions arbeiten, um jedes Objekt auf dem Chart anzulegen und zu pflegen.

Objekte werden mit Hilfe der ObjectCreate() Funktion erzeugt und der Typ des Objekts wird als ein ENUM_OBJECT Wert an die ObjectCreate() Funktion übertragen. Alle Objekte auf dem Chart haben ihre eigenen Eigenschaften, die entweder vom Typ ganzzahlig, double oder String sein können. Alle Eigenschaften werden durch speziell zugewiesene Funktionen angelegt und abgefragt: ObjectGetInteger(), ObjectSetInteger(), ObjectGetDouble(), ObjectSetDouble(), ObjectGetString(), ObjectSetString(). Es gibt zudem auch eine Funktion zum Löschen, Verschieben und Zählen der Objekte in jedem gegebenen Chart. 

Aufgrund des OOP-Ansatzes in MQL5, kann die Verarbeitung verschiedener Chartobjekte mit Hilfe der CChartObject Klasse und ihre Nachkommen erfolgen.

Die CChartObject Klasse ist eine Basisklasse für jedes der grafischen Objekte, die man in ein Chart platzieren kann. Bitte sehen Sie sich das grundlegende Vererbungs-Diagramm für CChartObject unten an

 

Vererbungs-Diagramm der CChartObject Klasse 

Abb. 1 Vererbungs-Diagramm der CChartObject Klasse


Wie wir gut erkennen können, sind einige Klassen durch ein kleines Dreieck unten links gekennzeichnet.

Diese Klassen sind die "Eltern" für andere Klassen.  Im Grunde erweitern Nachkommensklassen die Möglichkeiten der Basisklasse durch Hinzufügen neuer Variablen und Methoden, die auf dem Objekt agieren. Sie können sich auch in den Create() und Type() Methoden zur Erzeugung eines abgeleiteten Objekts unterscheiden und seine Art liefern.

Ich zeige Ihnen ein Beispiel: Die CChartObjectTrend Klasse ist die Elternklasse der CChartObjectTrendByAngle, CChartObjectChannel, CChartObjectStdDevChannel, CChartObjectRegression und CChartObjectPitchfork Klassen.

Die CChartObjectTrend Klasse ist eine Basisklasse für Objekte mit den Eigenschaften OBJPROP_RAY_RIGHT und OBJPROP_RAY_LEFT, und ist folgendermaßen definiert:

class CChartObjectTrend : public CChartObject
  {
public:
   //--- methods of access to properties of the object
   bool              RayLeft() const;
   bool              RayLeft(bool new_sel);
   bool              RayRight() const;
   bool              RayRight(bool new_sel);
   //--- method of creating the object
   bool              Create(long chart_id,string name,int window,
                                datetime time1,double price1,datetime time2,double price2);
   //--- method of identifying the object
   virtual int       Type() const { return(OBJ_TREND); }
   //--- methods for working with files
   virtual bool      Save(int file_handle);
   virtual bool      Load(int file_handle);
  };

Die Definition enthält Kommentare mit denen zwischen verschiedenen Arten von Methoden unterschieden werden kann.

Methoden zum Zugriff auf die Eigenschaften des Objekts sind RayLeft() und RayRight(). Sie werden implementiert, indem man die ObjectGetInteger() und ObjectSetInteger() Methoden aufruft, die auf dem CChartObjectTrend Objekt agieren.

bool CChartObjectTrend::RayLeft(bool new_ray)
  {
//--- checking
   if(m_chart_id==-1) return(false);
//---
   return(ObjectSetInteger(m_chart_id,m_name,OBJPROP_RAY_LEFT,new_ray));
  }

Die Create() Methode ist zuständig für die Erzeugung und das Anhängen des Objekts an ein Chart.

Sie ruft die ObjectCreate() Methode auf, mit OBJ_TREND als einen ihrer Parameter:

bool CChartObjectTrend::Create(long chart_id,string name,int window,
                                   datetime time1,double price1,datetime time2,double price2)
  {
   bool result=ObjectCreate(chart_id,name,OBJ_TREND,window,time1,price1,time2,price2);
   if(result) result&=Attach(chart_id,name,window,2);
//---
   return(result);
  }

Die Save() und Load() Methoden speichern und laden Objektdaten auf einer Festplatte ab, und zwar mit Hilfe der FileWriteInteger() und FileLoadInteger() Funktionen:

bool CChartObjectTrend::Save(int file_handle)
  {
   bool result;
//--- checking
   if(file_handle<=0) return(false);
   if(m_chart_id==-1) return(false);
//--- writing
   result=CChartObject::Save(file_handle);
   if(result)
     {
      //--- writing value of the "Ray left" property
      if(FileWriteInteger(file_handle,(int) ObjectGetInteger(m_chart_id,m_name,
                                                        OBJPROP_RAY_LEFT),CHAR_VALUE)!=sizeof(char))
      return(false);
      //--- writing value of the "Ray right" property
      if(FileWriteInteger(file_handle,(int) ObjectGetInteger(m_chart_id,m_name, 
                                                                OBJPROP_RAY_RIGHT),CHAR_VALUE)!=sizeof(char))
       return(false);
     }
//---
   return(result);
  }

bool CChartObjectTrend::Load(int file_handle)
  {
   bool result;
//--- checking
   if(file_handle<=0) return(false);
   if(m_chart_id==-1) return(false);
//--- reading
   result=CChartObject::Load(file_handle);
   if(result)
     {
      //--- reading value of the "Ray left" property
      if(!ObjectSetInteger(m_chart_id,m_name,OBJPROP_RAY_LEFT,
                                                 FileReadInteger(file_handle,CHAR_VALUE)))return(false);
      //--- reading value of the "Ray right" property
      if(!ObjectSetInteger(m_chart_id,m_name,OBJPROP_RAY_RIGHT,
                                                 FileReadInteger(file_handle,CHAR_VALUE))) return(false);
     }
//---
   return(result);
  }

Gehen wir rasch die Definitionen der Nachkommensklassen von CChartObjectTrend durch.

Die CChartObjectTrendByAngle Klasse fügt einen Angle() Eigenschaftsmodifikator hinzu und liefert den OBJ_TRENDBYANGLE Objekttyp:

class CChartObjectTrendByAngle : public CChartObjectTrend
  {
public:
   //--- methods of access to properties of the object
   double            Angle() const;
   bool              Angle(double angle);
   //--- method of creating the object
   bool              Create(long chart_id,string name,int window,datetime time1,double price1,
                               datetime time2,double price2);
   //--- method of identifying the object
   virtual int       Type() { return(OBJ_TRENDBYANGLE); }
  };

Die CChartObjectChannel Klasse liefert den OBJ_CHANNEL Objekttyp. Und da sie mit Kanäle umgeht, werden drei drei Paare Kurs/Daten-Parameter an die Create() Methode übertragen:

class CChartObjectChannel : public CChartObjectTrend
  {
public:
   //--- method of creating the object
   bool              Create(long chart_id,string name,int window,datetime time1,double price1,
                               datetime time2,double price2,datetime time3,double price3);
   //--- method of identifying the object
   virtual int       Type() const { return(OBJ_CHANNEL); }
  };

Die CChartObjectStdDevChannel Klasse fügt den Deviations() Eigenschaftsmodifikator und einen zusätzlichen Abweichungsparameter in der Create() Methode hinzu:

class CChartObjectStdDevChannel : public CChartObjectTrend
  {
public:
   //--- methods of access to properties of the object
   double            Deviations() const;
   bool              Deviations(double deviation);
   //--- method of creating the object
   bool              Create(long chart_id,string name,int window,
                           datetime time1,datetime time2,double deviation);
   //--- method of identifying the object
   virtual int       Type() const { return(OBJ_STDDEVCHANNEL); }
   //--- methods for working with files
   virtual bool      Save(int file_handle);
   virtual bool      Load(int file_handle);
  };

Die CChartObjectRegression Klasse erzeugt die Linie des Regressionstrends, und nur die Create() und Type() Methoden werden von denen in der CChartObjectTrend Klasse aufgehoben:

class CChartObjectRegression : public CChartObjectTrend
  {
public:
   //--- method of creating the object
   bool              Create(long chart_id,string name,int window,datetime time1,datetime time2);
   //--- method of identifying the object
   virtual int       Type() const { return(OBJ_REGRESSION); }
  };

Die CChartObjectPitchfork Klasse verarbeitet Forken-Typen, und auch hier werden nur die Create() und Type() Methoden verändert:

class CChartObjectPitchfork : public CChartObjectTrend
  {
public:
   //--- method of creating the object
   bool              Create(long chart_id,string name,int window,datetime time1,double price1,
                               datetime time2,double price2,datetime time3,double price3);
   //--- method of identifying the object
   virtual int       Type() const { return(OBJ_CHANNEL); }
  };

 Dieser kurze Überblick stellte die Grundregeln vor, die beim Schreiben neuer grafischer Objektklassen auf Grundlage einer anderen Klasse, angewendet werden:

Es müssen nicht alle Regeln angewandt werden - man kann durchaus nur neue Modifikatoren für den Zugriff oder neue Variablen und/oder Objekte in der Klasse hinzufügen.

Bevor wir fortfahren, möchte ich kurz noch erklären, wie man CChartObject Methoden bei grafischen Objekten einsetzt.

Anstatt mit der ObjectSet und ObjectGet Methodenfamilie zu arbeiten und Objekteigenschaften zu verwenden, genügt es CChartObject oder ein Nachkommens-Objekt zu deklarieren und Methoden in Gang zu bringen, die seine gewünschten Eigenschaften ändern. Um es leichter zu machen, hier ein Beispiel einer simplen Kennzeichnung. 

Anstatt folgendes zu schreiben:

void OnStart()
  {
//---
   string label_name="my_OBJ_LABEL_object";
   if(ObjectFind(0,label_name)<0)
     {
      Print("Object ",label_name," not found. Error code = ",GetLastError());
      ObjectCreate(0,label_name,OBJ_LABEL,0,0,0);           
      ObjectSetInteger(0,label_name,OBJPROP_XDISTANCE,200);
      ObjectSetInteger(0,label_name,OBJPROP_YDISTANCE,300);
      ObjectSetInteger(0,label_name,OBJPROP_COLOR,White);
      ObjectSetString(0,label_name,OBJPROP_TEXT,UP);
      ObjectSetString(0,label_name,OBJPROP_FONT,"Wingdings");
      ObjectSetInteger(0,label_name,OBJPROP_FONTSIZE,10);
      ObjectSetDouble(0,label_name,OBJPROP_ANGLE,-45);
      ObjectSetInteger(0,label_name,OBJPROP_SELECTABLE,false);
      ChartRedraw(0);                                      
     }
  }

Können wir es mit Hilfe der OOP-Ansatzes implementieren:

1. Deklarierung eines CChartObjectLabel Objekts:

CChartObjectLabel label;

2. Arbeit an dem Objekt: 

int OnInit()
  {
//---
   label.Create(0, label_name, 0, 0);
   label.X_Distance(200);
   label.Y_Distance(300);
   label.Color(White);
   label.Description(UP);
   label.Font("Wingdings");
   label.FontSize(10);
   label.Angle(-45);
   label.Selectable(false);
//---
   return(0);
  }

Sie sehen: der größte Unterschied liegt darin, dass wir nicht mehr länger auf einem String label_ name arbeiten: 

string label_name="my_OBJ_LABEL_object";

und die ObjectSetInteger(), ObjectGetInteger(), ObjectSetDouble(), ObjectGetDouble() Funktionen mit label_name als einen ihrer Parameter aufrufen, sondern das CChartObjectLabel Objekt deklarieren und uns seiner Methoden bedienen. Das kann man sich nicht nur leichter merken, sondern es ist auch weit logischer zu implementieren und schneller zu schreiben.

Wenn wir hinter der Objektinstanz einen Punkt (.) platzieren, stellt und der MQL5 Code-Editor die Funktionalität für die Vollendung des Codes zur Verfügung Wir müssen also nicht mehr in der MQL5 Dokumentation hin und her blättern, um herauszufinden, welche OBJPROP Eigenschaft wir setzen müssen, um eine gegebene Eigenschaft einzurichten oder zu bekommen.

Und ganz analog verhält es sich bei der vorhin beschriebenen CChartObjectTrend Klasse: um links oder rechts einen Strahl zu bekommen oder zu setzen, genügt es, das CChartObjectTrend Objekt zu deklarieren und die RayRight() oder RayLeft() Methode aufzurufen:

CChartObjectTrend trendline;
trendline.RayRight(true); 

2. ProgressBar

Als ersten Widget implementieren wir ProgressBar. Dieser Fortschrittsbalken zeigt den Fortschritt irgend eines Ablaufs von 0 bis x Prozent.

Damit das Widget stabiler wird, beschränken wir seinen Maximalwert nicht auf 100, sondern legen ihn auf jede positive ganze Zahl fest. Und wir brauchen einen farbigen Streifen, dessen Größe sich je nach Fortschritt ändert. Dabei denkt man zu aller erst daran, zwei Rechtecke zu verwenden, doch ich habe es anders gemacht - ich habe zwei CChartObjectEdit Objekte mit unterschiedlichen Hintergrundfarben genommen, eins innerhalb des anderen.

Das vereinfacht das Codieren, und ergänzender Text kann innerhalb des Fortschrittsbalkens zur Anzeige seines Wertes platziert werden. Außerdem wäre es schön, wenn unser Fortschrittsbalken waagrecht oder senkrecht wäre - je nachdem wie man ihn braucht.


2.1 ProgressBar Implementierung

Die CChartObjectProgress Klasse wird von der CChartObjectEdit Klasse abgeleitet.

Ich habe private interne Variablen hinzugefügt, die den Wert und Einschränkungen des Wert umfassen: m_value, m_min, m_max.

Die Richtung des Fortschrittsbalkens wird als ganzzahliger Wert eingerichtet und in der m_direction Variable umfasst. Die m_color Variable umfasst seine Farbe. Die Type() Methode liefert den OBJ_EDIT Wert, da für unseren Zweck hier ja sowieso kein Wert wiedererkannt wird. Vielleicht haben Sie die CChartObjectEdit m_bar Variable innerhalb der Klassendefinition bemerkt - dies ist der Innenbalken, dessen Größe sich, je nach dem m_Wert, verändert. Die zusätzlichen Variablen m_name und m_chart umfassen interne Werte für die m_bar Variable.

class CChartObjectProgressBar : public CChartObjectEdit
  {
private:
   int               m_value;
   int               m_min;
   int               m_max;
   int               m_direction;
   color             m_color;
   CChartObjectEdit  m_bar;
   string            m_name;
   long              m_chart_id;

public:
   int               GetValue();
   int               GetMin();
   int               GetMax();

   void              SetValue(int val);
   void              SetMin(int val);
   void              SetMax(int val);

   void              SetColor(color bgcol,color fgcol);
   bool              Create(long chart_id,string name,int window,int X,int Y,
                           int sizeX,int sizeY,int direction);

   //--- method of identifying the object
   virtual int       Type() const { return(OBJ_EDIT); }
};

Die Create() Methode erzeugt das ProgressBar Objekt und heftet es an das Chart.

Sie bemerken vielleicht, dass die Y-Variable von der sizeY-Variable abgezogen wird, sobald der senkrechte Balken gezeichnet wird. Das liegt daran, dass normalerweise CChartObjectEdit von oben nach unten gezeichnet wird und ich das innen liegende Rechteck von unten nach oben zeichnen wollte:

bool CChartObjectProgressBar::Create(long chart_id,string name,int window,int X,int Y,
                                          int sizeX,int sizeY,int direction=0)
  {
   bool result=ObjectCreate(chart_id,name,(ENUM_OBJECT)Type(),window,0,0,0);

   m_name=name;
   m_chart_id=chart_id;
   m_direction=direction;

   if(direction!=0)
     {
      Y=Y-sizeY;
     }

   ObjectSetInteger(chart_id,name,OBJPROP_BGCOLOR,White);
   ObjectSetInteger(chart_id,name,OBJPROP_COLOR,White);
   ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false);
   ObjectSetInteger(chart_id,name,OBJPROP_READONLY,true);

   result&=m_bar.Create(chart_id,name+"m_bar",window,X,Y,sizeX,sizeY);
   m_bar.Color(White);
   m_bar.ReadOnly(true);
   m_bar.Selectable(false);

//---
   if(result) result&=Attach(chart_id,name,window,1);
   result&=X_Distance(X);
   result&=Y_Distance(Y);
   result&=X_Size(sizeX);
   result&=Y_Size(sizeY);
//---
   return(result);
  }

Die SetColor() Methode erstellt die Farben für Hintergrund und Vordergrund beider Rechtecke:

void CChartObjectProgressBar::SetColor(color bgCol,color fgCol=White)
  {
   m_color=bgCol;
   m_bar.BackColor(m_color);
   m_bar.Color(fgCol);
  }

Die SetValue() Methode ist zuständig sowohl für das Einrichten des m_val Werts und die Neuberechnung der innen liegenden Rechteck-Objektgröße.

Die Größe wird für waagrechte und senkrechte Balken unterschiedlich berechnet:

void CChartObjectProgressBar::SetValue(int val)
  {
   if(m_direction==0) // horizontal ProgressBar
     {
      double sizex=(double)ObjectGetInteger(m_chart_id,m_name,OBJPROP_XSIZE,0);

      double stepSize=sizex/(m_max-m_min);

      m_value=val;
      m_bar.Create(m_bar.ChartId(),m_bar.Name(),m_bar.Window(),
                   m_bar.X_Distance(),m_bar.Y_Distance(),(int)MathFloor(stepSize*m_value),m_bar.Y_Size());
        } else {
      double sizey=(double)ObjectGetInteger(m_chart_id,m_name,OBJPROP_YSIZE,0);

      double stepSize=sizey/(m_max-m_min);
      m_value=val;
      m_bar.Create(m_bar.ChartId(),m_bar.Name(),m_bar.Window(),
                   m_bar.X_Distance(),(int)(this.Y_Distance()+sizey-MathFloor(stepSize*m_value)),
                   m_bar.X_Size(),(int)MathFloor(stepSize*m_value));

     }

   m_bar.Description(IntegerToString(m_value));
  }


2.2 ProgressBar Demo

Da wir die CChartObjectProgressBar Klasse ja bereits implementiert haben, sollten wir sie uns jetzt mal in Aktion ansehen.

Um einen neuen Fortschrittsbalken auf das Chart zu platzieren, reicht es aus, das CChartObjectProgressBar Objekt zu deklarieren und Create() und entsprechende Eigenschaftsmethoden zu verwenden:

progressBar.Create(0, "progressBar1", 0, 10, 10, 200, 40);
progressBar.SetColor(YellowGreen);
progressBar.SetMin(0);
progressBar.SetMax(100);
progressBar.SetValue(0);

Ich habe einen Demo-Expert Advisor geschrieben, der sechs verschiedene Fortschrittsbalken auf das Chart platziert und ihren wert verändert, nachdem irgendein Objekt auf dem Bildschirm angeklickt wird .

Der komplette Quellcode hierfür sowie weitere Demos sind im Anhang zu finden. Bitte sehen Sie sich die Präsentation unten an: 

 


3. Spinner

Das Spinner Widget ist ein Widget mit einem Feld und zwei Schaltflächen. Damit wird ein Wert im Bearbeitungsfeld durch Klicken auf eine der Schaltflächen entweder schrittweise angehoben oder verringert.

Beim Design des Objekts wollte ich nicht nur mit ganzzahligen Werten arbeiten, daher wurde Spinner so konzipiert, dass er auf double-Typen arbeiten kann. Spinner besitzt zudem auch die Möglichkeit, die Schrittgröße festzulegen, also den Wert zur schrittweisen Anhebung oder Senkung des aktuellen Werts. Zudem, sollte er auch einen Minimal- und Maximalwert besitzen, die nicht überschritten werden dürfen.


3.1 Spinner Implementierung

In MQL5 gibt es CChartObjectEdit und CChartObjectButton Klassen, die in eine Klasse, die CChartObjectSpinner Klasse, zusammengefasst werden können. CChartObjectSpinner erbt von CChartObjectEdit und enthält zwei private CChartObjectButton Mitgliedsobjekte.

Für den in den m_min und m_max Mitgliedervariablen gespeicherten Minimal- und Maximal m_value gibt es Beschränkungen, und die m_precision Variable speichert die Berechnungsgenauigkeit bis zum Wert der n. Ziffer. Diese Methoden werden für den Zugriff auf den Wert, die Einrichtung der Schrittgröße für Anhebung und Verringerung und die Einstellung des Werts gebraucht.

class CChartObjectSpinner: public CChartObjectEdit
  {

private:
   double            m_value;
   double            m_stepSize;
   double            m_min;
   double            m_max;
   int               m_precision;
   string            m_name;
   long              m_chart_id;
   CChartObjectButton m_up,m_down;

public:
   double            GetValue();
   double            GetMin();
   double            GetMax();

   void              SetValue(double val);
   void              SetMin(double val);
   void              SetMax(double val);

   double            Inc();
   double            Dec();

   bool              Create(long chart_id,string name,int window,int X,int Y,
                               int sizeX,int sizeY,double val,double stepSize,int precision);

   //--- method of identifying the object
   virtual int       Type() const { return(OBJ_EDIT); }
   
  };

Die Create() Methode erzeugt einen neuen CChartObjectSpinner und hängt ihn ans Chart an.

Rechts von CChartObjectEdit sind zwei CChartObjectButtons entstanden, von denen jede halb so groß ist wie CChartObjectEdit.

Die Schaltfläche zur Anhebung zeigt '+' und die zur Verringerung '-' .

bool CChartObjectSpinner::Create(long chart_id,string name,int window,int X,int Y,
                                     int sizeX,int sizeY,double val=0.0,double stepSize=1.0,int precision=8)
  {
   bool result=ObjectCreate(chart_id,name,(ENUM_OBJECT)Type(),window,0,0,0);

   m_name=name;
   m_chart_id=chart_id;
   m_value=val;
   m_stepSize=stepSize;
   m_precision=precision;

   ObjectSetInteger(chart_id,name,OBJPROP_BGCOLOR,White);
   ObjectSetInteger(chart_id,name,OBJPROP_COLOR,Black);
   ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false);
   ObjectSetInteger(chart_id,name,OBJPROP_READONLY,true);

   result&=m_up.Create(chart_id, name+"_up", window, X+sizeX, Y, 15, sizeY/2);
   result&=m_down.Create(chart_id, name+"_down", window, X+sizeX, Y+sizeY/2, 15, sizeY/2);
   m_up.Description("+");
   m_down.Description("-");
   ObjectSetString(chart_id,name,OBJPROP_TEXT,0,(DoubleToString(m_value,precision)));

//---
   if(result) result&=Attach(chart_id,name,window,1);
   result&=X_Distance(X);
   result&=Y_Distance(Y);
   result&=X_Size(sizeX);
   result&=Y_Size(sizeY);
//---
   return(result);
  }

Die SetValue() Methode setzt die private Variable m_value auf den doppelten Wert, falls sich dieser zwischen <m_min, m_max> befindet.

void CChartObjectSpinner::SetValue(double val)
  {
   if(val>=m_min && val<=m_max) m_value=val;
   this.Description(DoubleToString(m_value));
  }

Die Inc() Methode hebt den Wert schrittweise je nach vorgegebener Schrittgröße an, doch nie über den m_max Wert.

Bitte beachten Sie, dass ich zum Vergleich der doppelten Werte mit der gegebenen Exaktheit die NormalizeDouble() Funktion verwenden musste.

double CChartObjectSpinner::Inc(void)
  {
   if(NormalizeDouble(m_max-m_value-m_stepSize,m_precision)>0.0) m_value+=m_stepSize;
   else m_value=m_max;
   this.Description(DoubleToString(m_value, m_precision));
   m_up.State(false);
   return m_value;
  }

Die Dec() Methode verringert den Wert schrittweise je nach vorgegebener Schrittgröße, doch nie unter den m_min Wert.

double CChartObjectSpinner::Dec(void)
  {
   if(NormalizeDouble(m_value-m_stepSize-m_min,m_precision)>0.0)
      m_value-=m_stepSize; else m_value=m_min;
   this.Description(DoubleToString(m_value,m_precision));
   m_down.State(false);

   return m_value;
  }


3.2 Spinner Demo

So, dann testen wir mal die Spinner Objekte. Um sie verwenden zu können, genügt es das CChartObjectSpinner Objekt zu deklarieren und die Create(), SetMin() und SetMax() Methoden zu verwenden.

   spinner.Create(0, "spinner1", 0, 10, 10, 200, 40, 0.0, 0.4);
   spinner.SetMin(0);
   spinner.SetMax(100);

Ich habe Ihnen ein Demo vorbereitet, das drei Spinner-Widgets benutzt und alle Werte hinzufügt, nachdem die Spinner Schaltfäche angeklickt worden ist.

Dies alles geschieht innerhalb der OnChartEvent() Funktion;

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Check the event by pressing a mouse button
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
     
     if (sparam=="spinner1_up") spinner.Inc();
     if (sparam=="spinner1_down") spinner.Dec();
     if (sparam=="spinner2_up") spinner2.Inc();
     if (sparam=="spinner2_down") spinner2.Dec();
     if (sparam=="spinner3_up") spinner3.Inc();
     if (sparam=="spinner3_down") spinner3.Dec();
     
     label.Description(DoubleToString(NormalizeDouble(spinner.GetValue()+spinner2.GetValue()+spinner3.GetValue(),10),10));
   
   ChartRedraw();
     }
  }

 Bitte sehen sie sich das angehängt Demo an:

 

 

4. CChartObjectEditTable

In vielen Multi-Zeitebenenen (MTF) Expert Advisors werden Indikatorwerte für jede Zeitebene separat angezeigt.

Und manchmal werden für jede Zeitebene andere Indikatoreinstellungen in Form einer 2D-Tabelle von farblich unterschiedlichen Rechtecken oder Quadraten angezeigt. Ich habe für solche Objekte eine universelle 2D-Tabelle mit Hilfe der CChartObjectEditTable Klasse angelegt. Diese Klasse kann eine willkürliche Anzahl von Reihen und Spalten enthalten, da ich dazu das dynamische 2D-Objektarray benutzt habe.

Während des Designs musste ich für jede Zelle separat eine Farbe festlegen und auch die Möglichkeit mit berücksichtigen, jede Zelle mit einem anderen Textstring versehen zu können. Die Zellen sind alle gleich groß, doch ich wollte ihre Höhe, Breite und den Abstand zwischen ihnen festlegen.

4.1 CChartObjectEditTable Implementierung

Die CChartObjectEditTable Klasse enthält den CArrayObj Zeiger auf ein zweidimensionales Objektarray; die m_rows und m_columns Mitgliedervariablen enthalten die Anzahl der Reihen und Spalten in der Tabelle.

Eine m_baseName Mitgliedsvariable enthält das Präfix aller CChartObjectEdit Zellenobjekte innerhalb der Tabelle. Die GetColor(), SetColor(), GetText(), SetText() Methoden dienen zur Errichtung und zum Abruf der Farb- und Textwerte jeder gewünschten Zelle. Die Delete() Methode löscht alle von der Create() Methode erzeugten Objekte.

class CChartObjectEditTable
  {

private:
   CArrayObj        *array2D;
   int               m_rows;
   int               m_cols;
   string            m_baseName;

public:

   bool              Create(long chart_id,string name,int window,int rows,int cols,int startX,int startY,
                                int sizeX,int sizeY,color Bg,int deltaX,int deltaY);
   bool              Delete();
   bool              SetColor(int row,int col,color newColor);
   color             GetColor(int row,int col);
   bool              SetText(int row,int col,string newText);
   string            GetText(int row,int col);


  };

Die Create() Methode erzeugt eine zweidimensionale, dynamische Tabelle mit CChartObjectEdit Objekten.

Bitte sehen Sie sich genau an, wie man in MQL5 ein 2D-Objekte-Array erzeugt: Zuerst deklarieren wir einen Zeiger auf das 2D-Array, dann füllen wir das Array mit eienr Anzahl CArrayObj() Objekten - wir erzeugen quasi Arrays innerhalb des Arrays. All diese Arrays kann man als Behälter für die Tabellenspalten betrachten.

Jede Spalte enthält Reihen, die die CChartObjectEdit Objekte besitzen, wobei jedes Objekts eine jeweils extra anzeigende einzelne Zelle darstellt. 

bool CChartObjectEditTable::Create(long chart_id,string name,int window,int rows=1,int cols=1,
                                       int startX=0,int startY=0,int sizeX=15,int sizeY=15,
                                  color Bg=White,int deltaX=5,int deltaY=5)
  {
   m_rows=rows;
   m_cols=cols;
   m_baseName=name;
   int i=0,j=0;

   array2D=new CArrayObj();
   if (array2D==NULL) return false;
   
   for(j=0; j<m_cols; j++)
     {
      CArrayObj *new_array=new CArrayObj();
      if (array2D==NULL) return false;
   
      array2D.Add(new_array);
      for(i=0; i<m_rows; i++)
        {
         CChartObjectEdit *new_edit=new CChartObjectEdit();

         new_edit.Create(chart_id, name+IntegerToString(i)+":"+IntegerToString(j), window, 
                         startX+j*(sizeX+deltaX), startY+i*(sizeY+deltaY), sizeX, sizeY);
         new_edit.BackColor(Bg);
         new_edit.Color(White);
         new_edit.Selectable(false);
         new_edit.ReadOnly(true);
         new_edit.Description("");
         new_array.Add(new_edit);
        }
     }

   return true;
  }

Mit der SetColor() Methode lässt sich die Farbe jeder Zelle einrichten. Zuerst findet sie das Spalten-Array und dann das n. Elemente im Spalten-Array.

Durch Aufrufen der BackColor() Methode wird dann der Farbwert des Elements verändert.

bool CChartObjectEditTable::SetColor(int row,int col,color newColor)
  {
   CArrayObj *sub_array;
   CChartObjectEdit *element;

   if((row>=0 && row<m_rows) && (col>=0 && col<m_cols))
     {
      if(array2D!=NULL)
        {
         sub_array=array2D.At(col);
         element=(CChartObjectEdit*)sub_array.At(row);
         element.BackColor(newColor);

         return true;
        }
     }

   return false;
  }

Die GetColor() Methode arbeitet mit demselben Algorithmus wie die SetColor() Methode, um eine Zelle zu finden, liefert jedoch den Farbwert jeder gegebenen Zelle. 

color CChartObjectEditTable::GetColor(int row,int col)
  {
   CArrayObj *sub_array;
   CChartObjectEdit *element;

   if((row>=0 && row<m_rows) && (col>=0 && col<m_cols))
     {
      if(array2D!=NULL)
        {
         sub_array=array2D.At(col);
         element=(CChartObjectEdit*)sub_array.At(row);
         return element.BackColor();
        }
     }

   return NULL;
  }

Die SetText() Methode findet das Element und richtet durch Aufrufen der Description() Methode seinen Textwert ein.

bool CChartObjectEditTable::SetText(int row,int col,string newText)
  {
   CArrayObj *sub_array;
   CChartObjectEdit *element;

   if((row>=0 && row<m_rows) && (col>=0 && col<m_cols))
     {
      if(array2D!=NULL)
        {
         sub_array=array2D.At(col);
         element=(CChartObjectEdit*)sub_array.At(row);
         element.Description(newText);

         return true;
        }
     }

   return false;
  }

Die Delete() Methode löscht alle von der Create() Methode erzeugten Objekte.

Zuerst leert sie alle Spalten-Arrays und löscht dann das 2D Array-Objekt aus dem Memory.

bool CChartObjectEditTable::Delete(void)
  {
   for(int j=0; j<m_cols; j++)
     {
      CArrayObj *column_array=array2D.At(j);
      column_array.Clear();
      delete column_array;
     }
   delete array2D;
   return true;
  }


4.2 CChartObjectEditTable Demo

Um das CChartObjectEditTable Widget benutzten zu können, muss man das CChartEditTable Objekt deklarieren und die Create() Methode mit Parametern benutzen, die angeben wie viele Reihen und Spalten die Tabelle überhaupt haben soll.

Dann wendet man die Eigenschafts-Modifikatoren an und kann so ganz leicht bei jeder x-beliebigen Zelle Farbe und Text hinzufügen oder ändern.

table.Create(0,"t",0,1,10,10,10,15,15,Yellow);
table.SetColor(2,2,Red);
table.SetText(2,2,"2");

Bitte lesen Sie sich mein Skript durch, das alle Möglichkeiten, wie man das CChartObjectEditTable Objekt verwenden kann, erläutert.

Der Quellcode des Skripts befindet sich im Anhang an diesen Beitrag..

 

Fazit

In diesem Beitrag habe den Erzeugungsprozess neuer Chart-Widgets vorgestellt und beschrieben, die von einer CChartObject Klasse abgeleitet werden können. 

Die Verwendung implementierter Widgets ist ganz klar und erfordert nur wenige Zeilen Code.

Um mit diesen Widgets arbeiten zu können, müssen Sie die ChartObjectsExtControls.mqh Datei in den Expert Advisor oder den Indikatorcode mit aufnehmen.