English Русский 中文 Español 日本語 Português
Grafische Interfaces X: Das Standard Chart-Steuerelement (Build 4)

Grafische Interfaces X: Das Standard Chart-Steuerelement (Build 4)

MetaTrader 5Beispiele | 11 Januar 2017, 15:27
683 0
Anatoli Kazharski
Anatoli Kazharski

Inhalt


Einleitung

Der erste Artikel Grafisches Interface I: Vorbereiten der Bibliotheksstruktur (Kapitel 1) Beschreibt im Detail wofür diese Bibliothek gedacht ist. Am Ende von jedem Kapitel, finden Sie eine vollständige Liste mit Links zu diesem Artikel. Zudem finden Sie dort eine Möglichkeit das Projekt, entsprechend dem aktuellen Entwicklungsstand, herunterzuladen. Die Dateien müssen in den gleichen Verzeichnissen untergebracht werden, so, wie Sie auch in dem Archiv abgelegt sind.

Betrachten Sie wir ein anderes Steuerelement, das in der Bibliothek nicht feheln darf. Beim Start des Trading-Terminals, wird der Kurs-Chart für den Benutzer geöffnet Es wäre praktisch, wenn wir ein Werkzeug hätten, welches es uns ermöglicht, die Charts viel leichter verwalten zu können. Ein vorherige Artikel mit dem Namen MQL5 Cookbook: Monitoring Multiple Time Frames in a Single Window hat eine mögliche Variante eines solchen Tools demonstriert. Dieses Mal schreiben wir eine Klasse zum Erstellen eines Steuerelements, das einfach und bedienungsfreundlich in grafischen Benutzeroberflächen von Benutzerdefinierten MQL Anwendungen verwendet werden kann. Im Gegensatz zu der vorherigen unter dem oben aufgezeigten link erhältlichen Version, wird diese Implementation es ermöglichen, den Inhalt der Subcharts horizontal synchron zum Hauptfenster zu scrollen.

Darüber hinaus werden wir weiterhin den Bibliotheks-Code dahingehend optimieren, damit die CPU-Last verringert wird. Weitere Details werden in dem nachfolgenden Artikel ausgeführt.

 

Entwicklung einer Klasse für die Erstellung des Standard Chart-Steuerelementes

Bevor wir mit der Entwicklung der CStandardChart Klasse für die Erzeugung des Standard Chart controls fortfahren, muss die CSubChart Basisklasse mit zusätzlichen Eigenschaften (Sehen Sie sich dazu das nachfolgende Listing an) in der Object.mqh Datei ausgestattet werden. Dieses haben wir zuvor auch schon mit allen anderen Typen von grafischen Objekten, während der Entwicklung der Controls für diese Bibliothek, gemacht. Die Basisklasse für die CSubChart Klasse ist eine Klasse aus der Standardbibliothek — CChartObjectSubChart

//+----------------------------------------------------------------
//|                                                      Objects.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+----------------------------------------------------------------
...
//--- Liste der Klassen in der Datei für eine schnelle Navigation (Alt+G)
...
class CSubChart;
//+----------------------------------------------------------------
//| Klasse mit zusätzlichen Eigenschaften für das Subchart-Objekt         |
//+----------------------------------------------------------------
class CSubChart : public CChartObjectSubChart
  {
protected:
   int               m_x;
   int               m_y;
   int               m_x2;
   int               m_y2;
   int               m_x_gap;
   int               m_y_gap;
   int               m_x_size;
   int               m_y_size;
   bool              m_mouse_focus;
   //---
public:
                     CSubChart(void);
                    ~CSubChart(void);
   //--- Koordinaten
   int               X(void)                      { return(m_x);           }
   void              X(const int x)               { m_x=x;                 }
   int               Y(void)                      { return(m_y);           }
   void              Y(const int y)               { m_y=y;                 }
   int               X2(void)                     { return(m_x+m_x_size);  }
   int               Y2(void)                     { return(m_y+m_y_size);  }
   //--- Abstände von dem Eckpunkt(xy)
   int               XGap(void)                   { return(m_x_gap);       }
   void              XGap(const int x_gap)        { m_x_gap=x_gap;         }
   int               YGap(void)                   { return(m_y_gap);       }
   void              YGap(const int y_gap)        { m_y_gap=y_gap;         }
   //--- Größe
   int               XSize(void)                  { return(m_x_size);      }
   void              XSize(const int x_size)      { m_x_size=x_size;       }
   int               YSize(void)                  { return(m_y_size);      }
   void              YSize(const int y_size)      { m_y_size=y_size;       }
   //--- Focus
   bool              MouseFocus(void)             { return(m_mouse_focus); }
   void              MouseFocus(const bool focus) { m_mouse_focus=focus;   }
  };
//+----------------------------------------------------------------
//| Konstruktor                                                      |
//+----------------------------------------------------------------
CSubChart::CSubChart(void) : m_x(0),
                             m_y(0),
                             m_x2(0),
                             m_y2(0),
                             m_x_gap(0),
                             m_y_gap(0),
                             m_x_size(0),
                             m_y_size(0),
                             m_mouse_focus(false)
  {
  }
//+----------------------------------------------------------------
//| Destruktor                                                       |
//+----------------------------------------------------------------
CSubChart::~CSubChart(void)
  {
  }

Die CChartObjectSubChart Klasse enthält die Methoden für das Erzeugen des Subcharts, sowie auch alle Methoden für das Modifizieren der am häufigsten verwendeten Eigenschaften eines Charts. Die Liste enthält Methoden für das setzen und Abfragen der Eigenschaften wie: 

  • Koordinaten und Größenangaben
  • Symbol, Timeframe und Skalierung
  • Darstellung der Preis und Zeit Skalas.  

//+----------------------------------------------------------------
//|                                          ChartObjectSubChart.mqh |
//|                   Copyright 2009-2013, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+----------------------------------------------------------------
#include "ChartObject.mqh"
//+----------------------------------------------------------------
//| Class CChartObjectSubChart.                                      |
//| Verwendung: Klasse für das "SubChart" Object des Charts.                |
//|          Abgeleitet von der Klasse CChartObject.                        |
//+----------------------------------------------------------------
class CChartObjectSubChart : public CChartObject
  {
public:
                     CChartObjectSubChart(void);
                    ~CChartObjectSubChart(void);
   //--- Methoden für den Zugriff auf die Eigenschaften des Objektes
   int               X_Distance(void) const;
   bool              X_Distance(const int X) const;
   int               Y_Distance(void) const;
   bool              Y_Distance(const int Y) const;
   ENUM_BASE_CORNER  Corner(void) const;
   bool              Corner(const ENUM_BASE_CORNER corner) const;
   int               X_Size(void) const;
   bool              X_Size(const int size) const;
   int               Y_Size(void) const;
   bool              Y_Size(const int size) const;
   string            Symbol(void) const;
   bool              Symbol(const string symbol) const;
   int               Period(void) const;
   bool              Period(const int period) const;
   int               Scale(void) const;
   bool              Scale(const int scale) const;
   bool              DateScale(void) const;
   bool              DateScale(const bool scale) const;
   bool              PriceScale(void) const;
   bool              PriceScale(const bool scale) const;
   //--- Das Ändern der Zeit und Preis Koordinaten ist blockiert
   bool              Time(const datetime time) const { return(false); }
   bool              Price(const double price) const { return(false); }
   //--- Methoden für das Erzeugen des Objektes
   bool              Create(long chart_id,const string name,const int window,
                            const int X,const int Y,const int sizeX,const int sizeY);
   //--- Methoden für die Identifizierung des Objektes
   virtual int       Type(void) const { return(OBJ_CHART); }
   //--- Methoden für das Arbeiten mit Dateien
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
  };

Nun können wir die StandardChart.mqh Datei mit der CStandardChart Klasse erzeugen, in welcher die Standardmethoden für alle bibliotheks Controls angegeben werden, wie es in den nachfolgenden Programmcode gezeigt wird. Da das Steuerelement über einen horizontalen Scroll-Modus verfügen wird, benötigen wir ein Symbol für den Maus-Cursor, der dem Benutzer anzeigt, dass der scrolling Modus aktiviert ist und die Daten in den Subcharts verschoben werden, da sich der Mauszeiger horizontal bewegt hat. Um das Icon verändern zu können, beziehen wir die Pointer.mqh Datei, welche die CPointer Klasse beinhaltet mit ein.Diese Klasse haben wir zuvor in dem Artikel Graphische Interfaces VIII: Das Baumasicht-Control (Kapitel 2) besprochen. Als Symbol für den Mauszeiger verwenden wir eine Kopie des aktiviem Zeigers mit dem für das horizontale Scrollen der Hauptcharts (doppelseitiger schwarzer Pfeil mit einem weißen Umriss). Am Ende des Artikels finden die finden Sie zwei Versionen von diesem Icon (schwarze and blaue Pfeile). 

Dementsprechend wurde die Enumeration des Mauszeigers (ENUM_MOUSE_POINTER) um einen weiteren Bezeichner ergänzt(MP_X_SCROLL): 

//+----------------------------------------------------------------
//| Enumeration der Pointer-Typen                                 |
//+----------------------------------------------------------------
enum ENUM_MOUSE_POINTER
  {
   MP_CUSTOM     =0,
   MP_X_RESIZE   =1,
   MP_Y_RESIZE   =2,
   MP_XY1_RESIZE =3,
   MP_XY2_RESIZE =4,
   MP_X_SCROLL   =5
  };

Zusätzlich ist es notwendig die Resourcen mit diesen Icons für diesen Typ von Mauszeigern in der Pointer.mqh Datei mit einzubeziehen, und die Schalter(switch)-Konstruktion in der CPointer::SetPointerBmp() Methode sollte um einem weiteren Case-Block erweitert werden.

//+----------------------------------------------------------------
//|                                                      Pointer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+----------------------------------------------------------------
#include "Element.mqh"
//--- Resources
...
#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll.bmp"
#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll_blue.bmp"
//+----------------------------------------------------------------
//| Festlegen der Cursor-Bilder entsprechend des Cursor-Typs                       |
//+----------------------------------------------------------------
void CPointer::SetPointerBmp(void)
  {
   switch(m_type)
     {
      case MP_X_RESIZE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs.bmp";
         break;
      case MP_Y_RESIZE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs.bmp";
         break;
      case MP_XY1_RESIZE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_xy1_rs_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_xy1_rs.bmp";
         break;
      case MP_XY2_RESIZE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_xy2_rs_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_xy2_rs.bmp";
         break;
      case MP_X_SCROLL :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll.bmp";
         break;
     }
//--- Falls dir benutzerdefinierte Typ (MP_CUSTOM) angegeben ist
   if(m_file_on=="" || m_file_off=="")
      ::Print(__FUNCTION__," > Both images must be set for the cursor!");
  }

Es sollte auch beachtet werden, dass die Moving() Methode in zwei Modi verwendet werden kann, was über das dritte Argument der Methode festgelegt wird. Der Standardwert für dieses Argument ist false, was bedeutet, dass ein Control nur bewegt werden kann, falls das Formular, zu welchem es hinzugefügt worden ist, sich ebenfalls in dem Bewegungsmodus befindet. 

//+----------------------------------------------------------------
//|                                                StandardChart.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+----------------------------------------------------------------
#include "Element.mqh"
#include "Window.mqh"
#include "Pointer.mqh"
//+----------------------------------------------------------------
//| Klasse für die Erzeugung eines Standard-Charts                              |
//+----------------------------------------------------------------
class CStandardChart : public CElement
  {
private:
   //--- Ein Pointer zu der Form zu welchem das Element hinzugefügt worden ist
   CWindow          *m_wnd;
   //---
public:
   //--- Speichert den Pointer
   void              WindowPointer(CWindow &object)          { m_wnd=::GetPointer(object); }
   //---
public:
   //--- Chart Eventhandler
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Bewegen des Elementes
   virtual void      Moving(const int x,const int y,const bool moving_mode=false);
   //--- (1) Anzeigen, (2) verstecken, (3) zurücksetzen, (4) löschen
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) Setzen, (2) Zurücksetzen der Prioritäten der linken Maustaste
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
   //---
private:
   //--- Veränderung der Breite an der rechten Ecke des Fensters
   virtual void      ChangeWidthByRightWindowSide(void);
   //--- Verändern der Höhe an der unteren Ecke des Fensters
   virtual void      ChangeHeightByBottomWindowSide(void);
  };
//+----------------------------------------------------------------
//| Konstruktor                                                      |
//+----------------------------------------------------------------
CStandardChart::CStandardChart(void)
  {
//--- Abspeichern des namens der Elementklasse in der Basisklasse
   CElement::ClassName(CLASS_NAME);
  }
//+----------------------------------------------------------------
//| Destruktor                                                       |
//+----------------------------------------------------------------
CStandardChart::~CStandardChart(void)
  {
  }

Falls der Wert des dritten Argumentes in der Moving() Methode auf true gesetzt wird, dann wird das Control auf jeden Fall bewegt, unabhängig davon, ob sich das Formular in dem Bewegungsmodus befindet. In einigen Fällen kann dieses die CPU-Auslastung signifikant reduzieren. 

Um festzustellen zu können, ob sich das Formular im Bewegungsmodus befindet, wurde die ClampingAreaMouse() Methode zu der CWindow Klasse hinzugefügt. Sie gibt den Bereich, wo die linke Maustaste gedrückt worden war, zurück: 

//+----------------------------------------------------------------
//| Klasse für das Erzeugen einer Form mit Controls                           |
//+----------------------------------------------------------------
class CWindow : public CElement
  {
public:
   //--- Gib den Bereich zurück wo die linke Maustaste gedrückt wurde
   ENUM_MOUSE_STATE  ClampingAreaMouse(void)                           const { return(m_clamping_area_mouse);          }
  };

Die CWindow::ClampingAreaMouse() Methode kann nur über den Formular-Pointer in jedem hinzugefügten Control zu dieser Form aufgerufen werden. Damit alles so funktioniert, wie wir es oben beschrieben haben, muss noch ein Programm Block innerhalb der Moving() Methode eines jedem Controls hinzugefügt werden, so wie es das nachfolgende Listing zeigt. (Sehen Sie sich dazu das in Gelb markierte Fragment an). Als Beispiel wird hier die Methode der CSimpleButton (kurze version) Klasse gezeigt. 

//+----------------------------------------------------------------
//| Bewegen der Controls                                                  |
//+----------------------------------------------------------------
void CSimpleButton::Moving(const int x,const int y,const bool moving_mode=false)
  {
//--- Abbrechen, falls das Element versteckt ist
   if(!CElement::IsVisible())
      return;
//--- Falls die Verwaltung auf das Fenster gerichtet ist, Identifizierung der Position
   if(!moving_mode)
      if(m_wnd.ClampingAreaMouse()!=PRESSED_INSIDE_HEADER)
         return;
//--- Wenn der Ankerpunkt die rechte Seite ist
//--- If the anchored to the left
//--- Wenn der Ankerpunkt unten ist
//--- If the anchored to the top
//--- Aktualisieren der Koordinaten in den grafischen Objekten
   m_button.X_Distance(m_button.X());
   m_button.Y_Distance(m_button.Y());
  }

Ein Beispiel für die Verwendung der Moving() Methode kann in den nachfolgenden listing angesehen werden, welche den Code der CSimpleButton::Show() Methode demonstriert. In diesem Fall werden die Control-Koordinaten gewaltsam aktualisiert, daher wurde true als drittes Argument übergeben. Entsprechende Änderungen wurden in allen Klassen der Bibliothek vorgenommen, welche die Moving() Methode verwenden.  

//+----------------------------------------------------------------
//| Zeigt den Button                                                 |
//+----------------------------------------------------------------
void CSimpleButton::Show(void)
  {
//--- Verlassen, falls das Element bereits sichtbar ist
   if(CElement::IsVisible())
      return;
//--- Alle Objekte sichtbar machen
   m_button.Timeframes(OBJ_ALL_PERIODS);
//--- Status der Sichtbarkeit
   CElement::IsVisible(true);
//--- Aktualisiere die Positionen der Objekte
   Moving(m_wnd.X(),m_wnd.Y(),true);
  }

Die Optimierung der entwickelten Bibliothek werden wir später in einem Artikel besprechen. Jetzt werden wir erstmal ein Standard Chart-Control besprechen.

Lassen Sie uns nun die Möglichkeit schaffen, eine Serie von Sub-Charts in einer Reihe platzieren zu können. Dafür ist es notwendig, dynamische Arrays von Objekten zu deklarieren, welche Charts repräsentieren, so wie bestimmte Eigenschaften, wie zum Beispiel: (1) Chart Bezeichner, (2) Symbol und (3) Timeframe. Bevor wir das Standard Chart control erzeugen, ist es notwendig die CStandardChart::AddSubChart() Methode zu verwenden, in welcher das Chart-Symbol und die Timeframe angegeben werden. Zu Beginn dieser Methode, bevor Elemente zu dem Array hinzugefügt werden und diese mit den übergebenen Parametern initialisiert werden, gibt es noch eine Überprüfung über die Verfügbarkeit des Symbols unter Verwendung der CStandardChart::CheckSymbol() Methode.  

class CStandardChart : public CElement
  {
private:
   //--- Objekte für die Erzeugung des Elementes
   CSubChart         m_sub_chart[];
   //--- Chart-Eigenschaften:
   long              m_sub_chart_id[];
   string            m_sub_chart_symbol[];
   ENUM_TIMEFRAMES   m_sub_chart_tf[];
   //---
public:
   //--- Fügt vor der Erzeugung einen Chart mit den angegebenen Parameter hinzu
   void              AddSubChart(const string symbol,const ENUM_TIMEFRAMES tf);
   //---
private:
   //--- Überprüfung des Symbols
   bool              CheckSymbol(const string symbol);
  };

Die CStandardChart::CheckSymbol() Methode überprüft zunächst, ob das angegebene Symbol in dem MarketWatch-Window verfügbar ist. Wenn das Symbol nicht gefunden wird, wird ein Versuch unternommen das Symbol in der allgemeinen Liste zu finden. Wenn das Symbol gefunden wird, muss es zu der MarketWatch-Liste hinzugefügt werden. Andernfalls wird es nicht möglich sein den Subchart mit diesem Symbol zu erzeugen (Stattdessen wird ein Sub-Chart mit dem Symbol des Haupt-Charts erzeugt). 

Im Erfolgsfall gibt die CStandardChart::CheckSymbol() Methode true zurück. Falls das Symbol nicht gefunden wurde gibt die Method false zurück, Und der Subchart wird nicht hinzugefügt (Arrays haben die gleiche Größe), und es wird darüber eine Nachricht in dem Journal ausgegeben.  

//+----------------------------------------------------------------
//| Fügt einen Chart hinzu                                                     |
//+----------------------------------------------------------------
void CStandardChart::AddSubChart(const string symbol,const ENUM_TIMEFRAMES tf)
  {
//--- Prüft, ob ein Symbol auf dem Server verfügbar ist
   if(!CheckSymbol(symbol))
     {
      ::Print(__FUNCTION__," > Symbol "+symbol+" is not available on the server!");
      return;
     }
//--- Vergrößern des Arrays um ein Element
   int array_size=::ArraySize(m_sub_chart);
   int new_size=array_size+1;
   ::ArrayResize(m_sub_chart,new_size);
   ::ArrayResize(m_sub_chart_id,new_size);
   ::ArrayResize(m_sub_chart_symbol,new_size);
   ::ArrayResize(m_sub_chart_tf,new_size);
//--- Abspeichern des Wertes des übergebenen Parameters
   m_sub_chart_symbol[array_size] =symbol;
   m_sub_chart_tf[array_size]     =tf;
  }
//+----------------------------------------------------------------
//| Überprüfung der Verfügbarkeit eines Symbols                      |
//+----------------------------------------------------------------
bool CStandardChart::CheckSymbol(const string symbol)
  {
   bool flag=false;
//--- Überprüfung des Symbols in den MarketWatch window
   int symbols_total=::SymbolsTotal(true);
   for(int i=0; i<symbols_total; i++)
     {
      //--- Wenn das Symbol verfügbar ist, Ende des Zyklus
      if(::SymbolName(i,true)==symbol)
        {
         flag=true;
         break;
        }
     }
//--- Wenn das Symbol im Fenster "Market Watch" nicht verfügbar ist
   if(!flag)
     {
      //---... versuch es in der allgemeinen Liste zu finden
      symbols_total=::SymbolsTotal(false);
      for(int i=0; i<symbols_total; i++)
        {
         // ---Wenn dieses Symbol verfügbar ist
         if(::SymbolName(i,false)==symbol)
           {
            //---... füge es dem Marktbeobachtungsfenster hinzu und beende den Zyklus
            ::SymbolSelect(symbol,true);
            flag=true;
            break;
           }
        }
     }
//--- gib die Suchergebnisse zurück
   return(flag);
  }

Das Erstellen eines Standard-Chart-Steuerelements erfordert drei Methoden: Eine übergeordnete public Methode und zwei private Methoden, eine von denen bezieht sich auf das Symbol für den Maus-cursor im horizontalen Scroll-Modus. Die CStandardChart::SubChartsTotal() public Methode wurde als eine zusätzliche Methode zur Klasse hinzugefügt, um die Anzahl der Unterdiagramm abrufen zu können. 

class CStandardChart : public CElement
  {
private:
   //--- Objekte für die Erzeugung des Elementes
   CSubChart         m_sub_chart[];
   CPointer          m_x_scroll;
   //---
public:
   //--- Methoden zum Erstellen einer Stndard-Charts
   bool              CreateStandardChart(const long chart_id,const int subwin,const int x,const int y);
   //---
private:
   bool              CreateSubChart(void);
   bool              CreateXScrollPointer(void);
   //---
public:
   //--- Gibt die Größe des Arrays für Charts zurück
   int               SubChartsTotal(void)              const { return(::ArraySize(m_sub_chart)); }
  };

Betrachten Sie wir die CStandardChart::CreateSubCharts()-Methode zum Erstellen von Unterdiagrammen. Hier findet eine Überprüfung der Anzahl der Charts in dem Array statt, die vor der Erzeugung des Controls zu Beginn der Methode hinzugefügt wurden. Falls keine hinzugefügt wurden, dann beendet das Programm an dieser Stelle die Methode und schreibt eine entsprechende Nachricht in das Journal.

Wenn ein Chart hinzugefügt worden ist, dann wird die Breite für jedes Objekt berechnet. Die Gesamtbreite eines Controls muss von dem User in der benutzerdefinierten Klasse der MQL Anwendung definiert werden. Falls es notwendig ist mehrere Charts zu erzeugen, dann ist es ausreichend die gesamte Breite des Controls mit der Anzahl der Charts zu dividieren, um die Breite für jedes einzelne Objekt zu erhalten.

Anschließend werden die Objekte innerhalb einer Schleife erzeugt. Hierbei wird auch die Positionierung berücksichtigt. (Ankerpunkte zu einer der Seiten des Formulars). Dieser Punkt wurde schon in den vorherigen Artikel beschrieben und wird daher hier nicht weiter besprochen. 

Nach der Erzeugung des Subcharts, muss derBezeichne des erstellten Charts abgefragt und in dem Array gespeichert werden. Ebenso müssen seine Eigenschaften gesetzt und gespeichert werden. 

//+----------------------------------------------------------------
//| Erzeugen der Charts                                              |
//+----------------------------------------------------------------
bool CStandardChart::CreateSubCharts(void)
  {
//--- Abfrage der Anzahl der Charts
   int sub_charts_total=SubChartsTotal();
//--- Meldung ausgeben, falls es in dieser Gruppe keinen Chart gibt
   if(sub_charts_total<1)
     {
      ::Print(__FUNCTION__," > This method is to be called, "
              "if a group contains at least one chart! Use the CStandardChart::AddSubChart() method");
      return(false);
     }
//--- Berechnung der Koordinaten und der Größen
   int x=m_x;
   int x_size=(sub_charts_total>1)? m_x_size/sub_charts_total : m_x_size;
//--- Erzeugen der angegebenen Anzahl von Charts
   for(int i=0; i<sub_charts_total; i++)
     {
      //--- Den Objektnamen bilden
      string name=CElement::ProgramName()+"_sub_chart_"+(string)i+"__"+(string)CElement::Id();
      //--- Berechnung der x-Koordinate
      x=(i>0)?(m_anchor_right_window_side)? x-x_size+1 :  x+x_size-1 : x;
      //--- Die Breite des letzten Charts einstellen
      if(i+1>=sub_charts_total)
         x_size=m_x_size-(x_size*(sub_charts_total-1)-(sub_charts_total-1));
      //--- Einen Button einrichten
      if(!m_sub_chart[i].Create(m_chart_id,name,m_subwin,x,m_y,x_size,m_y_size))
         return(false);
      //--- Abfragen und Abspeichern des Bezeichners des erzeugten Charts
      m_sub_chart_id[i]=m_sub_chart[i].GetInteger(OBJPROP_CHART_ID);
      //--- Setzen der Eigenschaften
      m_sub_chart[i].Symbol(m_sub_chart_symbol[i]);
      m_sub_chart[i].Period(m_sub_chart_tf[i]);
      m_sub_chart[i].Z_Order(m_zorder);
      m_sub_chart[i].Tooltip("\n");
      //--- Abspeichern der Größe
      m_sub_chart[i].XSize(x_size);
      m_sub_chart[i].YSize(m_y_size);
      //--- Ränder von den Kanten
      m_sub_chart[i].XGap((m_anchor_right_window_side)? x : x-m_wnd.X());
      m_sub_chart[i].YGap((m_anchor_bottom_window_side)? m_y : m_y-m_wnd.Y());
      //--- Abspeichern des Objekt-Pointers
      CElement::AddToArray(m_sub_chart[i]);
     }
//---
   return(true);
  }

Es ist auch nachträglich noch möglich alle Eigenschaften eines Sub-Charts innerhalb des Standard Charts zu verändern. Dieses geschieht mithilfe des Pointers, welche über die Methode CStandardChart::GetSubChartPointer() abgerufen werden kann. Falls aus Versehen ein falscher Index übergeben wurde, wird dieser korrigiert um eine Überschreitung der Grenzen des Arrays zu vermeiden. 

class CStandardChart : public CElement
  {
public:
   //--- Gibt den Pointer zu dem Chart über den angegebenen Index zurück
   CSubChart        *GetSubChartPointer(const uint index);
  };
//+----------------------------------------------------------------
//| Gibt den Pointer zu dem Chart über den angegebenen Index zurück              |
//+----------------------------------------------------------------
CSubChart *CStandardChart::GetSubChartPointer(const uint index)
  {
   uint array_size=::ArraySize(m_sub_chart);
//--- Falls es keinen Chart gibt, dann benachrichtigen
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > This method is to be called, "
              "if a group contains at least one chart!");
     }
//--- Korrekturen, falls die Größe überschritten wird
   uint i=(index>=array_size)? array_size-1 : index;
//--- Rückgabe des Pointers
   return(::GetPointer(m_sub_chart[i]));
  }

Es wird nur dann ein Icon für den Mauszeiger erzeugt, falls der horizontale Scroll-Modus für die Daten in den SubCharts aktiviert ist. Dieser sollte mit Hilfe der Methode CStandardChart::XScrollMode() vor der Erzeugung des Controls aktiviert werden.  

class CStandardChart : public CElement
  {
private:
   //--- Horizontales Scroll-Modus
   bool              m_x_scroll_mode;
   //---
public:
   //--- Horizontales Scroll-Modus
   void              XScrollMode(const bool mode) { m_x_scroll_mode=mode; }
  };
//+----------------------------------------------------------------
//| Erzeugen des Mauszeigers für das horizontale Scrollen                            |
//+----------------------------------------------------------------
bool CStandardChart::CreateXScrollPointer(void)
  {
//--- Abbrechen, falls das horizontale Scrollen nicht gebraucht wird
   if(!m_x_scroll_mode)
      return(true);
//--- Festlegen der Eigenschaften
   m_x_scroll.XGap(0);
   m_x_scroll.YGap(-20);
   m_x_scroll.Id(CElement::Id());
   m_x_scroll.Type(MP_X_SCROLL);
//--- Erzeugung des Elementes
   if(!m_x_scroll.CreatePointer(m_chart_id,m_subwin))
      return(false);
//---
   return(true);
  }

Zusammenfassung. Falls der horizontales Scrollen muss aktiviert wurde, dann verwendet diese Operation die CStandardChart::HorizontalScroll() Methode, welche in dem Eventhandler des Controls aufgerufen wird, sobald das CHARTEVENT_MOUSE_MOVE Event ausgelöst wird. Falls die linke Maustaste gedrückt ist, wird, in Abhängigkeit ob es sich um einen aktuellen Mausklick handelt oder ob es sich um sich wiederholende Aufrufe zu der Methode während des Prozesses des horizontalen Scrollens handelt, der Abstand vom ersten Klick und der aktuell zurückgelegten Strecke in Pixeln berechnet. Hier: 

  • Das Formular ist gesperrt.
  • Die Koordinaten für das Icon des Mauszeigers werden berechnet.
  • Das Icon wird angezeigt.

Der berechnete Wert für das Verschieben der Daten in den Sub-Charts kann negativ sein, da der Offset relativ zu der letzten Bar berechnet wird - Die::ChartNavigate() Methode mit dem CHART_END Wert (Das zweite Argument) aus der ENUM_CHART_POSITION Enumeration. Falls der Wert positiv ist, verlässt das Programm die Methode. Wenn die Überprüfungen erfolgreich abgeschlossen wurde, dann wird der aktuelle Wert für die Verschiebung für den nächsten Durchlauf abgespeichert . Anschließend werden die "Auto Scroll" (CHART_AUTOSCROLL) und "Chart shift from the right border" (CHART_SHIFT) in allen Sub-Charts deaktiviert und die Verschiebung wird entsprechend des berechneten Wertes durchgeführt.

Wenn die Maustaste losgelassen wird, dann wird das Formular entsperrt und das Icon des Mauszeigers, welches den Prozess der horizontalen Verschiebung anzeigt, wird versteckt. Anschließend verlässt das Programm die Methode. 

class CStandardChart : public CElement
  {
private:
   //--- Die Variablen für das horizontale Scrollen des Charts
   int               m_prev_x;
   int               m_new_x_point;
   int               m_prev_new_x_point;
   //---
private:
   //--- Horizontales Scrolling
   void              HorizontalScroll(void);
  };
//+----------------------------------------------------------------
//| Horizontales Scrollen des Charts                                |
//+----------------------------------------------------------------
void CStandardChart::HorizontalScroll(void)
  {
//--- Abbrechen, falls das horizontale Scrollen des Charts deaktiviert ist
   if(!m_x_scroll_mode)
      return;
//--- Falls die Maustaste gedrückt wurde
   if(m_mouse.LeftButtonState())
     {
      //--- Abspeichern der aktuellen x-Koordinate des Mauszeigers
      if(m_prev_x==0)
        {
         m_prev_x      =m_mouse.X()+m_prev_new_x_point;
         m_new_x_point =m_prev_new_x_point;
        }
      else
         m_new_x_point=m_prev_x-m_mouse.X();
      //--- Blockieren der Form
      if(!m_wnd.IsLocked())
        {
         m_wnd.IsLocked(true);
         m_wnd.IdActivatedElement(CElement::Id());
        }
      //--- Aktualisiere die Koordinaten des Pointers und mache sie sichtbar
      int l_x=m_mouse.X()-m_x_scroll.XGap();
      int l_y=m_mouse.Y()-m_x_scroll.YGap();
      m_x_scroll.Moving(l_x,l_y);
      //--- Zeige die Pointer
      m_x_scroll.Show();
      //--- Setze das visibility(sichtbarkeist) flag
      m_x_scroll.IsVisible(true);
     }
   else
     {
      m_prev_x=0;
      //--- Die Form entsperren
      if(m_wnd.IdActivatedElement()==CElement::Id())
        {
         m_wnd.IsLocked(false);
         m_wnd.IdActivatedElement(WRONG_VALUE);
        }
      //--- Verstecke den Pointer
      m_x_scroll.Hide();
      //--- Setze das visibility(sichtbarkeist) flag
      m_x_scroll.IsVisible(false);
      return;
     }
//--- Abbrechen, falls wir einen positiven Wert haben
   if(m_new_x_point>0)
      return;
//--- Abspeichern der aktuellen Position
   m_prev_new_x_point=m_new_x_point;
//--- Allen Charts zuweisen
   int symbols_total=SubChartsTotal();
//--- Deaktivierung des Autoscroll-Modus und der Verschiebung von der rechten Seite
   for(int i=0; i<symbols_total; i++)
     {
      if(::ChartGetInteger(m_sub_chart_id[i],CHART_AUTOSCROLL))
         ::ChartSetInteger(m_sub_chart_id[i],CHART_AUTOSCROLL,false);
      if(::ChartGetInteger(m_sub_chart_id[i],CHART_SHIFT))
         ::ChartSetInteger(m_sub_chart_id[i],CHART_SHIFT,false);
     }
//--- Zurücksetzen der letzten Fehlermeldung
   ::ResetLastError();
//--- Verschiebe die Charts
   for(int i=0; i<symbols_total; i++)
      if(!::ChartNavigate(m_sub_chart_id[i],CHART_END,m_new_x_point))
         ::Print(__FUNCTION__," > error: ",::GetLastError());
  }

Die CStandardChart::ZeroHorizontalScrollVariables() Methode wird dafür benutzt, die Hilfsvariablen des horizontalen Scroll-Modus für die Sub-Charts zurückzusetzen. Es kann auch erforderlich sein bis zur letzten war programmatisch vorzugehen. In diesem Fall wird die CStandardChart::ResetCharts() Methode verwendet. 

class CStandardChart : public CElement
  {
public:
   //--- Zurücksetzen des Charts
   void              ResetCharts(void);
   //---
private:
   //--- Zurücksetzen der Variablen für das horizontale Scrollen
   void              ZeroHorizontalScrollVariables(void);
  };
//+----------------------------------------------------------------
//| Zurücksetzen der Charts                                          |
//+----------------------------------------------------------------
void CStandardChart::ResetCharts(void)
  {
   int sub_charts_total=SubChartsTotal();
   for(int i=0; i<sub_charts_total; i++)
      ::ChartNavigate(m_sub_chart_id[i],CHART_END);
//--- Zurücksetzen der Hilfsvariablen für das horizontale Scrollen der Charts
   ZeroHorizontalScrollVariables();
  }
//+----------------------------------------------------------------
//| Zurücksetzen der Variablen für das horizontale Scrollen          |
//+----------------------------------------------------------------
void CStandardChart::ZeroHorizontalScrollVariables(void)
  {
   m_prev_x           =0;
   m_new_x_point      =0;
   m_prev_new_x_point =0;
  }

Es könnte auch notwendig sein, ein Klick der linken Maustaste über einem Sub-Chart eines Standard-Charts nachzuverfolgen. Dafür fügen wir den ON_CLICK_SUB_CHART Bezeichner der Defines.mqh Datei hinzu: 

//+----------------------------------------------------------------
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+----------------------------------------------------------------
...
//--- Event Bezeichner
...
#define ON_CLICK_SUB_CHART         (28) // Clicking the subchart

Um bestimmen zu können, ob auf einen sub-Chart geklickt wurde, implementieren wir die CStandardChart::OnClickSubChart() Method. Wenn die Überprüfungen für den Namen und den Bezeichner erfolgreich abgeschlossen werden (Sehen Sie dazu das nachfolgende Listing), dann wird eine Nachricht generiert mit (1) demON_CLICK_SUB_CHART Bezeichner des Events, (2) dem Bezeichner des Controls, (3) dem Index des Sub-Charts und (4) dem Symbolnamen. 

class CStandardChart : public CElement
  {
private:
   //--- Verarbeitung eines Klicks auf den Sub-Chart
   bool              OnClickSubChart(const string clicked_object);
  };
//+----------------------------------------------------------------
//| Verarbeiten eines Klicks auf einen Button                        |
//+----------------------------------------------------------------
bool CStandardChart::OnClickSubChart(const string clicked_object)
  {
//--- Abbrechen, falls der Klick nicht auf diesem Menüpunkt stattgefunden hat
   if(::StringFind(clicked_object,CElement::ProgramName()+"_sub_chart_",0)<0)
      return(false);
//--- Abfragen des Bezeichners und des Indexes über den Objektnamen
   int id=CElement::IdFromObjectName(clicked_object);
//--- Abbrechen, falls der Bezeichner nicht übereinstimmt
   if(id!=CElement::Id())
      return(false);
//--- Abfrage des Index
   int group_index=CElement::IndexFromObjectName(clicked_object);
//--- Eine Nachricht darüber senden
   ::EventChartCustom(m_chart_id,ON_CLICK_SUB_CHART,CElement::Id(),group_index,m_sub_chart_symbol[group_index]);
   return(true);
  }

Angenommen, Sie benötigen eine andere Art der Navigation in Sub-Charts, ähnlich wie es für den Hauptchart mit den Mitteln des Terminals implementiert wird. Drückt man die Taste «Space» oder «Enter» im Terminal MetaTrader , dann wird ein Bearbeitungsfeld in der unteren linken Ecke des Diagramms aktiviert (siehe Screenshot unten). Dieses ist eine Art einer Befehlszeile, wo ein Datum eingegeben werden kann, um bis dahin auf dem Diagramm zu springen. Diese Befehlszeile kann auch verwendet werden, um das Symbol und den Zeitrahmen des Charts zu ändern.  

 Abbildung  1. Befehlszeile des Diagramms an der linken Ecke.

Abbildung 1. Befehlszeile des Diagramms an der linken Ecke.


Übrigens, es wurde ein neues Feature für die Verwaltung der Befehlszeile in das neueste Update des Handelsterminals (build 1455) hinzugefügt.

8. MQL5: Die neue eigenschaft CHART_QUICK_NAVIGATION erlaubt das aktivieren und deaktivieren der schnellnavigation in den Charts. Falls Sie den Status dieser Eigenschaft ändern wollen, verwenden Sie die ChartSetInteger und ChartGetInteger Funktionen.

Die Navigations-Bar wird über einen Druck auf die Enter oder Space-Taste geöffnet. Diese erlaubt es Ihnen, sehr schnell zu einem bestimmten Datum innerhalb des Charts zu springen, sowie ein neues Symbol und eine neue Timeframe auszuwählen Falls ihr eigenes Programm diese Tasten abfragen sollte, dann deaktivieren Sie die Eigenschaft, CHART_QUICK_NAVIGATION damit es zu keinen Wechselwirkungen mit dem Terminal kommt. Die Schnellnavigation kann auch weiterhin durch einen Doppelklick geöffnet werden.

… 

Innerhalb der grafischen Oberfläche kann alles bequemer und einfacher erfolgen. Die Easy And Fast Bibliothek beinhaltet bereits das Kalender Control (die CCalendar Klasse). Die Navigation in dem Hauptchart und dem Sub-Chart kann über die Auswahl eines Datums in dem Kalender implementiert werden. Lassen Sie uns alles bis auf eine einzige Methode mit einem Argument vereinfachen. Der Wert dieses Arguments ist das Datum, wohin der Chart verschoben werden soll. Wir nennen diese Methode CStandardChart::SubChartNavigate(). Der nachfolgende Programmtext zeigt die aktuelle Version dieser Methode.

Zu Beginn dieser Methode werden die "Auto Scroll" und "Shift from the right border of the chart" Modi des Haupt-Charts deaktiviert. Anschließen, wenn die Variable der Methode übergeben worden und größer ist, als der Anfang des aktuellen Tages, gehen wir einfach zu der letzten Bar und verlassen die Methode. Falls das Datum kleiner ist, dann ist es notwendig die Anzahl der Bars zu berechnen, um die nach links verschoben werden muss. Zunächst wird die Berechnung für den Hauptchart durchgeführt:

  • Abfrage der gesamten Anzahl von Bars für das aktuelle Symbol und die aktuelle Timeframe vom Anfang des aktuellen Tages bis zu dem angegebenen Datum.
  • Abfrage der sichtbaren Bars auf dem Chart
  • Abfrage der Anzahl der Bars von Beginn des aktuellen Tages + zwei Bars als eine zusätzliche Einrückung.
  • Berechnung der Anzahl der Bars für die Verschiebung von der letzten bar

Anschließend wird der Hauptchart nach links verschoben und das gleiche wird für alle Sub-Charts wiederholt. 

class CStandardChart : public CElement
  {
public:
   //--- Springe zu dem angegebenen Datum
   void              SubChartNavigate(const datetime date);
  };
//+----------------------------------------------------------------
//| Springe zu dem angegebenen Datum                                 |
//+----------------------------------------------------------------
void CStandardChart::SubChartNavigate(const datetime date)
  {
//--- (1) Das aktuelle Datum des Charts und (2) das neue in dem Kalender ausgewählte Datum
   datetime current_date  =::StringToTime(::TimeToString(::TimeCurrent(),TIME_DATE));
   datetime selected_date =date;
//--- Deaktivierung des Autoscroll-Modus und der Verschiebung von der rechten Seite
   ::ChartSetInteger(m_chart_id,CHART_AUTOSCROLL,false);
   ::ChartSetInteger(m_chart_id,CHART_SHIFT,false);
//--- Falls das ausgewählte Datum größer als das aktuelle Datum ist
   if(selected_date>=current_date)
     {
      //--- Gehe bei allen Charts zu dem aktuellen Datum
      ::ChartNavigate(m_chart_id,CHART_END);
      ResetCharts();
      return;
     }
//--- Abfrage der Anzahl der Bars von dem angegebenen Datum
   int  bars_total    =::Bars(::Symbol(),::Period(),selected_date,current_date);
   int  visible_bars  =(int)::ChartGetInteger(m_chart_id,CHART_VISIBLE_BARS);
   long seconds_today =::TimeCurrent()-current_date;
   int  bars_today    =int(seconds_today/::PeriodSeconds())+2;
//--- Setzen des Abstandes von der rechten Seite für alle Charts
   m_prev_new_x_point=m_new_x_point=-((bars_total-visible_bars)+bars_today);
   ::ChartNavigate(m_chart_id,CHART_END,m_new_x_point);
//---
   int sub_charts_total=SubChartsTotal();
   for(int i=0; i<sub_charts_total; i++)
     {
      //--- Deaktivierung des Autoscroll-Modus und der Verschiebung von der rechten Seite
      ::ChartSetInteger(m_sub_chart_id[i],CHART_AUTOSCROLL,false);
      ::ChartSetInteger(m_sub_chart_id[i],CHART_SHIFT,false);
      //--- Abfrage der Anzahl der Bars von dem angegebenen Datum
      bars_total   =::Bars(m_sub_chart[i].Symbol(),(ENUM_TIMEFRAMES)m_sub_chart[i].Period(),selected_date,current_date);
      visible_bars =(int)::ChartGetInteger(m_sub_chart_id[i],CHART_VISIBLE_BARS);
      bars_today   =int(seconds_today/::PeriodSeconds((ENUM_TIMEFRAMES)m_sub_chart[i].Period()))+2;
      //--- Abstand von der rechten Seite des Charts
      m_prev_new_x_point=m_new_x_point=-((bars_total-visible_bars)+bars_today);
      ::ChartNavigate(m_sub_chart_id[i],CHART_END,m_new_x_point);
     }
  }

Die Entwicklung der CStandardChart Klasse für die Erzeugung des Standard Chart Controls ist fertig. Lassen Sie uns nun eine Anwendung schreiben, um uns ansehen zu können wie es funktioniert. 


Anwendung zum Testen des Steuerelements

Für den Test können Sie einen Expert Advisor aus dem vorherigen Artikel verwenden. Entfernen Sie alle Controls, mit Ausnahme des Hauptmenüs, der Statusbar und den Tabs. Machen Sie es so, dass jeder Tab seine separate Gruppe von Sub-Charts hat Jede Gruppe enthält eine bestimmte Währung; Somit wird auch jedes hat seine eigene Beschreibung/Beschriftung haben:

  • Das erste Tab - EUR (Euro).
  • Das zweite Tab - GBP (Great Britain Pound).
  • Das dritte Tab - AUD (Australian Dollar).
  • Das vierte Tab - CAD (Canadian Dollar).
  • Das fünfte Tab - JPY (Japanese Yen).

Die Sub-Charts werden innerhalb des Arbeitsbereichs der Tabs gesetzt und sie werden automatisch in ihrer Größe verändert, wenn sich die Größe des Formulars ändert. Die rechte Seite des Arbeitsbereiches in den Tabs hat immer einen Abstand von 173 Pixeln von der rechten Kante des Formulars. Dieser Platz wird mit Controls für das Setzen von Eigenschaften wie

  • Anzeige des zeitlichen Rahmens (Date time).
  • Die Preisscala anzeigen(Price scale).
  • Ändern der Charts-Timeframe (Timeframes).
  • Navigation über den Kalender.

Als Beispiel ist es ausreichend, den Programmcode für die Erzeugung eines einzelnen Standard Charts zu zeigen (CStandardChart) Control. Denken Sie daran, dass das horizontale Scrollen eines Charts standardmäßig deaktiviert ist. Daher kann, falls es gebraucht wird, die CStandardChart::XScrollMode() Methode verwendet werden. Die CStandardChart::AddSubChart() Methode wird dazu verwendet, Charts der Gruppe hinzuzufügen.

//+----------------------------------------------------------------
//| Kasse für das erzeugende Anwendung                               |
//+----------------------------------------------------------------
class CProgram : public CWndEvents
  {
protected:
   //--- Standard chart
   CStandardChart    m_sub_chart1;
   //---
protected:
   //--- Standard chart
   bool              CreateSubChart1(const int x_gap,const int y_gap);
  };
//+----------------------------------------------------------------
//| Erzeuge Standard chart 1                                          |
//+----------------------------------------------------------------
bool CProgram::CreateSubChart1(const int x_gap,const int y_gap)
  {
//--- Abspeichern des Fenster-Pointers
   m_sub_chart1.WindowPointer(m_window);
//--- Hinzufügen zum ersten Tab
   m_tabs.AddToElementsArray(0,m_sub_chart1);
//--- Koordinaten
   int x=m_window.X()+x_gap;
   int y=m_window.Y()+y_gap;
//--- Festlegen der Eigenschaften vor der Erzeugung
   m_sub_chart1.XSize(600);
   m_sub_chart1.YSize(200);
   m_sub_chart1.AutoXResizeMode(true);
   m_sub_chart1.AutoYResizeMode(true);
   m_sub_chart1.AutoXResizeRightOffset(175);
   m_sub_chart1.AutoYResizeBottomOffset(25);
   m_sub_chart1.XScrollMode(true);
//--- Hinzufügen des Charts
   m_sub_chart1.AddSubChart("EURUSD",PERIOD_D1);
   m_sub_chart1.AddSubChart("EURGBP",PERIOD_D1);
   m_sub_chart1.AddSubChart("EURAUD",PERIOD_D1);
//--- Erzeugung des Controls
   if(!m_sub_chart1.CreateStandardChart(m_chart_id,m_subwin,x,y))
      return(false);
//--- Hinzufügen des Objektes zu dem gemeinsamen Array von Objektgruppen
   CWndContainer::AddToElementsArray(0,m_sub_chart1);
   return(true);
  }

Der nachfolgende Screenshot zeigt das endgültige Ergebnis. In diesem Beispiel können die Daten des Sub-Charts horizontal gescrollt werden, so wie es auch in dem Hauptchart der Fall ist. Darüber hinaus funktioniert die Navigation zwischen den Sub-Charts über den Kalender, inklusive der schnellen weiterleitung der Daten.

 Abbildung  2. Test des Standard Chart Controls.

Abbildung 2. Test des Standard Chart Controls.


Die Testanwendung, die in diesem Artikel vorgestellt wird, kann mit dem unten aufgeführten Link heruntergeladen werden. 

 

Optimierung der Timer und Event-Handler der Bibliotheks-Engine

Zuvor haben wir die Easy And Fast Bibliothek nur in dem Windows 7 x64 Betriebssystem getestet . Nach einem Update zu Windows 10 x64, haben wir herausgefunden, dass die CPU Auslastung signifikant steigt. Diese Bibliothek verbrauchte bis zu 10% der CPU-Leistung, auch wenn sie sich im Standby-Modus befunden hat und es keine Interaktion mit dem grafischen Interface gab. Die nachfolgenden Screenshots zeigen die Belastung der CPU vor und nach dem Hinzufügen der Testanwendung zu dem Chart

Abbildung 3. Auslastung der CPU vor dem Hinzufügen der MQL Anwendung zu dem Chart.

Abbildung 3. Auslastung der CPU vor dem Hinzufügen der MQL Anwendung zu dem Chart.


Abbildung 4. Auslastung der CPU nach dem Hinzufügen der MQL Anwendung zu dem Chart.

Abbildung 4. Auslastung der CPU nach dem Hinzufügen der MQL Anwendung zu dem Chart.


Wir haben herausgefunden, dass das Problem in dem Timer der Bibliotheks-Engine liegt, der denChart alle 16ms updated, wie es in dem nachfolgenden Programmcode gezeigt wird:

//+----------------------------------------------------------------
//| Timer                                                            |
//+----------------------------------------------------------------
void CWndEvents::OnTimerEvent(void)
  {
//--- Verlassen, falls das Array leer ist  
   if(CWndContainer::WindowsTotal()<1)
      return;
//--- Überprüfen der Events von allen Elementen des Timers
   CheckElementsEventsTimer();
//--- Neuzeichnen des Charts
   m_chart.Redraw();
  }

Die Auslastung der CPU steigt sogar noch weiter, wenn wir die Bewegung des Mauszeigers innerhalb des Arbeitsbereiches des Charts hinzufügen und Interaktionen mit dem grafischen Interface der MQL Anwendung ausführen. Unsere Aufgabe ist es nun die Operationen der Bibliotheks-Engine zu reduzieren und das wiederholte Neuzeichnen des Charts mit jedem Timer-Event bei einer Mausbewegung zu verhindern. Wie können wir das lösen?

Entfernen Sie die Zeile, die für das Neuzeichnen des Charts verantwortlich ist(Hervorgehoben in Rot) aus der CWndEvents::ChartEventMouseMove() Methode: 

//+----------------------------------------------------------------
//| CHARTEVENT MOUSE MOVE event                                      |
//+----------------------------------------------------------------
void CWndEvents::ChartEventMouseMove(void)
  {
//--- Abbrechen, wenn es sich nicht um eine Bewegung des Mauszeigers handelt
   if(m_id!=CHARTEVENT_MOUSE_MOVE)
      return;
//--- Verschieben des Fensters
   MovingWindow();
//--- Festlegen des Chart-Status
   SetChartState();
//--- Neuzeichnen des Charts
   m_chart.Redraw();
  }

Der Timer ist zurzeit verantwortlich für den Farbwechsel der Controls, wenn sich die Maus darüber befindet und das schnelle Vorspulen von verschiedenen Controls (Listen, Tabellen, Kalender etc) .Für diese Funktionen muss er konstant arbeiten. Um Ressourcen zu sparen, wird der Timer nur aktiviert, wenn der Mauszeiger anfängt sich zu bewegen und er wird deaktiviert, wenn die Bewegung des Mauszeigers stoppt. 

Um diese Idee umzusetzen, müssen wir einige Dinge der CMouse Klasse hinzufügen. Zu diesen Ergänzungen gehört einen Zähler für die Aufrufe zu dem System Timer und der CMouse::GapBetweenCalls() Methode, welche die Differenz zwischen den Aufrufen zu dem Mauszeiger-Events zurück gibt. 

class CMouse
  {
private:
   //--- Counter für die Aufrufe
   ulong             m_call_counter;
   //---
public:   
   //--- Gibt (1) den Wert des Zählers der nach dem letzten Aufruf gespeichert wurde und(2) die Differenz zwischen den Aufrufen zu dem Eventhandler der Mauszeiger-Bewegung-Events zurück.
   ulong             CallCounter(void)     const { return(m_call_counter);                  }
   ulong             GapBetweenCalls(void) const { return(::GetTickCount()-m_call_counter); }
  };
//+----------------------------------------------------------------
//| Konstruktor                                                      |
//+----------------------------------------------------------------
CMouse::CMouse(void) : m_call_counter(::GetTickCount())
  {
  }

Die Logik ist recht einfach. Sobald sich der Mauszeiger zu bewegen beginnt, speichert der Eventhandler der CMouse Klasse den aktuellen Wert des System.Timers

//+----------------------------------------------------------------
//| Verarbeiten der Events von der Bewegung des Mauszeigers          |
//+----------------------------------------------------------------
void CMouse::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Verarbeiten des Mauszeiger Bewegungs Events
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //---Koordinaten und der Status der linken Maustaste
      m_x                 =(int)lparam;
      m_y                 =(int)dparam;
      m_left_button_state =(bool)int(sparam);
      //--- Speichere den Wert von dem Zähler für die Aufrufe
      m_call_counter=::GetTickCount();
      //--- Abfrage der Position des Mauszeigers
      if(!::ChartXYToTimePrice(0,m_x,m_y,m_subwin,m_time,m_level))
         return;
      //--- Abfrage der relativen y-Koordinate
      if(m_subwin>0)
         m_y=m_y-m_chart.SubwindowY(m_subwin);
     }
  }

Der Timer der Bibliotheks-Engine (die CWndEvents Klasse) muss die folgende Bedingungen beinhalten: Wenn sich der Mauszeiger in den letzten 500ms nicht bewegt hat, sollte der Chart nicht neu gezeichnet werden. Zu diesem Zeitpunkt muss die linke Maustaste losgelassen sein, damit wir die Situation, wo ein schnelles Vorspulen für eine Controls aktiv ist, ausschließen. 

//+----------------------------------------------------------------
//| Timer                                                            |
//+----------------------------------------------------------------
void CWndEvents::OnTimerEvent(void)
  {
//--- Verlassen, wenn der Mauszeiger im Ruhezustand ist (Die Differenz zwischen L aufrufen ist größer als 500 Millisekunden) und die linke Maustaste ist nicht gedrückt.
   if(m_mouse.GapBetweenCalls()>500 && !m_mouse.LeftButtonState())
      return;
//--- Verlassen, falls das Array leer ist  
   if(CWndContainer::WindowsTotal()<1)
      return;
//--- Überprüfen der Events von allen Elementen des Timers
   CheckElementsEventsTimer();
//--- Neuzeichnen des Charts
   m_chart.Redraw();
  }

Problem gelöst. Das Deaktivieren des Neuzeichnens, wenn sich der Mauszeiger bewegt, hat keinen Einfluss auf die Qualität für das Bewegen eines Formulars mit Steuerelementen, da ein Wert von 16ms im Timer-Intervall völlig ausreichend für das Neuzeichnen ist. Dieses Problem wurde jetzt auf eine einfache Art und Weise gelöst, aber es gibt auch noch andere Lösungsmöglichkeiten. Die Optimierung des Bibliothekscodes wird weiter in den kommenden Artikeln der Serie besprochen werden, denn es gibt noch andere Methoden und Möglichkeiten, die helfen können den CPU-Verbrauch effizienter zu machen.


Optimierung der Strukturansicht und der Controls des Datei-Navigators

Wir haben auch herausgefunden, dass die Initialisierung eines einer Baumansicht (CTreeView) sehr viel Zeit benötigt, wenn sich eine größere Anzahl von Elementen in dieser Ansicht befindet. Dieses passiert ebenfalls in dem Datei-Navigator (CFileNavigator), welcher auf diesen Listen-Typ zurückgreift. Um dieses Problem zu lösen, ist es notwendig den dritten Parameter bei der Reservierung von Speicherplatz für einen Array anzugeben::ArrayResize()

Auszug aus der ::ArrayResize() Funktions-Referenz:

Bei einer häufigen Speicherzuordnung empfiehlt sich der Einsatz des dritten Parameters, der eine Reserve festlegt, der die Anzahl der physischen Speicherzuweisungen reduziert. Alle nachfolgenden Aufrufe von ArrayResize führen nicht zu einer physischen Umverteilung des Speichers. Wir erinnern daran, dass der dritte Parameter nur während der physischen Speicherzuweisung verwendet wird...

Zum Vergleich haben wir nachfolgend die Ergebnisse von verschiedenen Tests mit verschiedenen Werten für die Größe der Reservierung von Arrays in einer Baumansicht aufgeführt: Die Anzahl der Dateien für diesen Test erreicht 15 000.

 Abbildung 5. Testergebnisse für das Bilden von Arrays mit Werten für zu reservierenden Speicherplatz

Abbildung 5. Testergebnisse für das Bilden von Arrays mit Werten für zu reservierenden Speicherplatz


Legen Sie die reservierte Größe für Arrays der Strukturansicht auf 10 000 fest. Die entsprechenden Änderungen wurden in den CTreeView und CFileNavigator Klassen vorgenommen.

 

Neue Icons für Verzeichnisse und Dateien in dem Datei-Navigator

Dem Datei-Navigator wurden neue Icons für Ordner und Dateien hinzugefügt (die CFileNavigator Klasse), welche denen von Windows 10 entsprechen.. Ihr schlankes Design eignet sich besser für grafische Oberflächen, aber ggf. können auch benutzerdefinierte Versionen verwendet werden.

 Abbildung 6. Neue Icons für Verzeichnisse und Dateien in dem Datei-Navigator

Abbildung 6. Neue Icons für Verzeichnisse und Dateien in dem Datei-Navigator 

 

Diese Bilder sind am Ende des Artikels erhältlich.

 

Schlussfolgerung

Die Bibliothek für das Erzeugen von grafischen Interfaces sieht zu dem aktuellen Stand der Entwicklung wie folgt aus: (Schematisch).

 Abbildung 6. Die Struktur unserer Bibliothek zum aktuellen Stand der Entwicklung

Abbildung 6. Die Struktur unserer Bibliothek zum aktuellen Stand der Entwicklung


Die nächsten Artikel über das grafische Interface führen die Entwicklung der Easy And Fast Bibliothek fort. Die Bibliothek wird um zusätzliche Controls erweitert, die bei der Entwicklung von MQL Anwendungen nötig sein könnten. Die vorhandenen Steuerelemente werden verbessert und mit neuen Features ergänzt. 

Nachfolgend können Sie die vierte Version (build 4) der Easy And Fast Bibliothek herunterladen. Wenn Sie interessiert sind, können Sie zu einer schnelleren Entwicklung dieses Projektes durch Vorschläge und Lösungen in Form von Kommentaren oder privaten Nachrichten beitragen.

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

Beigefügte Dateien |
Entwickeln und testen einer Strategie für Binäre Optionen mit dem Strategie-Tester des MetaTrader 4 Entwickeln und testen einer Strategie für Binäre Optionen mit dem Strategie-Tester des MetaTrader 4
Eine Anleitung für das Erstellen und Testen einer Strategie für Binäre Optionen im Strategie-Tester des MetaTrader 4 mit dem "Binary-Options-Strategy-Tester" aus dem Market.
Die Muster, die beim Handeln der Währungskörbe erreichbar sind Die Muster, die beim Handeln der Währungskörbe erreichbar sind
In Folge des letzten Artikels über die Prinzipien des Handelns der Währungskörbe werden die Muster betrachtet, die ein Trader selbst finden kann. Es wurden positive und negative Seiten jedes Musters betrachtet und es gibt Hinweise bezüglich ihrer Verwendung. Als Mittel für die Analyse wurden die Indikatoren verwendet, die aufgrund des Indikators Williams erstellt sind.
Grafische Interfaces X: Text Edit Box, Bild Slider und einfache Controls (build 5) Grafische Interfaces X: Text Edit Box, Bild Slider und einfache Controls (build 5)
In diesem Artikel besprechen wir neue Controls: Text Edit Box, Bild-Slider, sowie weitere zusätzliche einfache Controls: Text-Label und Bild. Die Bibliothek wächst weiter, und neben der Einführung der neuen Steuerelemente, werden auch die zuvor erstellten verbessert.
Der universell Oszillator mit dem graphischen Interface Der universell Oszillator mit dem graphischen Interface
Im Artikel wird die Erstellung des universellen Indikators aufgrund aller Oszillators des Terminalen mit dem eigenen graphischen Interface beschrieben. Es ermöglicht schnell und bequem die Parameter jedes separaten Oszillators aus dem Chart-Fenster zu wechseln (und ohne Öffnung des Fensters der Eigenschaften), ihre Messwerte zu vergleichen und für sich die optimale Variante für eine konkrete Aufgabe zu wählen.