Graphical Interfaces I: Animation des graphischen Interfaces (Kapitel 3)

Anatoli Kazharski | 30 Mai, 2016

Inhalt

 

Einleitung

Dieser Artikel ist die Fortsetzung des ersten Teils dieser Serie über grafische Interfaces. Der erste Artikel Grafisches Interface I: Vorbereiten der Bibliotheksstruktur (Kapitel 1) beschreibt im Detail wofür diese Bibliothek gedacht ist. Eine vollständige Liste mit Links zu diesem Artikel des ersten Teils, finden Sie am Ende von jedem Kapitel. Zudem finden Sie dort eine Möglichkeit das Projekt, entsprechend dem aktuellen Entwicklungszustand, herunterzuladen. Die Dateien müssen in den gleichen Verzeichnissen untergebracht werden, so, wie Sie auch in dem Archiv abgelegt sind.

In dem vorherigen Artikel dieser Seria, haben wir damit begonnen ein Formular (eine Form) für Steuerelemente (Controls) zu entwickeln. In diesem Artikel werden wir die Klasse mit Methoden auffüllen, welche es möglich machen, das Formular über den Chartbereich zu bewegen. Wir werden anschließend diese Komponente in den Kern der Bibliothek mit aufnehmen. Zudem werden wir sicherstellen, dass sich die Farbe der Form verändert, sobald sich die Maus darüber befindet.

 

Verwaltung des grafischen Interfaces

Es wird zunächst eine Form mit Controls zu dem Chart hinzugefügt. Im Augenblick ist diese noch vollkommen starr. Unser Ziel ist es, dass diese Form mit ihren Controls auf die Aktionen des Anwenders reagiert. Dafür müssen wir zunächst die Mauszeigerposition auf dem Chart verfolgen können. Das Programm muss die Position des Mauszeigers zu jeder Zeit beachten. Dieses ist mit den Standard-Chartparametern einer MQL Anwendung nicht möglich. Das Verfolgen der Mausposition und das Drücken vom Maustasten muss eingeschaltet werden. Daher müssen wir für die Zukunft einige Chart Eigenschaften ändern. Dafür werden wir die CChart Klasse der Standardbibliothek mit in die WndEvents.mqh Datei mit in unsere Bibliothek mit aufnehmen und eine Instanz von ihr in dem Klassen-Körper (class body) erzeugen.

//+------------------------------------------------------------------+
//|                                                    WndEvents.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Defines.mqh"
#include "WndContainer.mqh"
#include <Charts\Chart.mqh>
//+------------------------------------------------------------------+
//| Klasse für das Behandeln von Events                              |
//+------------------------------------------------------------------+
class CWndEvents : public CWndContainer
  {
protected:
   CChart            m_chart;
  };

In dem Konstruktor der Klasse fügen wir das Objekt dem aktuellen Charts hinzu, und wir aktivieren das Verfolgen der Position des Mauszeigers. In dem Destruktor, entfernen wir das Objekt von dem Chart (in den nachfolgenden Code in grün hervorgehoben). Andernfalls, wenn das Programm von dem Chart entfernt wird, wird sich der Chart selbst schließen.

//+-----------------------------------------------------------------+
//| Konstruktor                                                     |
//+-----------------------------------------------------------------+
CWndEvents::CWndEvents(void) : m_chart_id(0),
                               m_subwin(0),
                               m_indicator_name(""),
                               m_program_name(PROGRAM_NAME)
  {
//--- Abfragen der ID des aktuellen Charts
   m_chart.Attach();
//--- Aktivieren der Verfolgung von Maus-Events
   m_chart.EventMouseMove(true);
  }
//+-----------------------------------------------------------------+
//| Destruktor                                                      |
//+-----------------------------------------------------------------+
CWndEvents::~CWndEvents(void)
  {
//--- Entfernen vom Chart
   m_chart.Detach();
  }

Wie schon zuvor erwähnt, besitzt jede Klasse eines Controls seinen eigenen Eventhandler. Lassen Sie uns nun in der CWindow Klasse einige Methoden hinzufügen, die wir für das Verwalten der Form benötigen. Alle diese Methoden werden in der CWindow::OnEvent() Methode aufgerufen. Lassen Sie uns je nach Funktionalität entscheiden, welche der Methoden derCWindow Klasse hinzugefügt werden:

1. Es muss bestimmt werden, in welchem Fenster sich der Mauszeiger gerade befindet. Das müssen wir deshalb feststellen, weil ein Chart aus mehreren Teilen bestehen kann. Z.B. aus dem Hauptchart und einem Indikator-Unterfenster. Die MQL Anwendung kann also ein Indikator sein, welcher sich nicht in dem Hauptfenster befindet.

2. Falls sich also unsere MQL Anwendung als Indikator in einem Unterfenster befindet, dann muss die y-Koordinate angepasst werden.

3. Der Status der linken Maustaste muss ebenso überprüft werden, sowie der Punkt, an welchem die Taste gedrückt wurde. Es gibt vier Zustände:

  • NOT_PRESSED — Die Taste ist nicht gedrückt.
  • PRESSED_OUTSIDE — Die Taste wurde außerhalb des Bereichs des Formulars gedrückt.
  • PRESSED_INSIDE_WINDOW — Die Taste wurde innerhalb des Bereichs des Formulars gedrückt.
  • PRESSED_INSIDE_HEADER — Die Taste wurde im Bereich des Fenster-Kopfes gedrückt (Header).

Fügen Sie die ENUM_WMOUSE_STATE Enumeration der Enums.mqh Datei hinzu:

//+------------------------------------------------------------------+
//|                                                        Enums.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Enumertaion über die verschiedenen Zustände der linken Maustaste |
//+------------------------------------------------------------------+
enum ENUM_WMOUSE_STATE
  {
   NOT_PRESSED           =0,
   PRESSED_OUTSIDE       =1,
   PRESSED_INSIDE_WINDOW =2,
   PRESSED_INSIDE_HEADER =3
  };

4. Wenn sich der Mauszeiger innerhalb des Bereiches einer Form oder innerhalb des Bereiches einer Interface Komponente befindet, dann muss das Scrollen des Charts und das Trading-Level-Management deaktiviert werden.

5. Wenn sich der Mauszeiger innerhalb des Bereiches des Fenster-Kopfes (Headers) befindet und die linke Maustaste gedrückt wird, dann muss das Programm in den Modus für das Aktualisieren der Koordinaten eintreten.

6. Wenn die Koordinaten aktualisiert wurden, muss geprüft werden, ob der Bereich des Charts verlassen wurde. In diesem Fall müssen dann noch entsprechende Einstellungen / Anpassungen vorgenommen werden. Eine für eine solche Überprüfung wird das (1) Objekt der CChart Klasse benötigt, sowie (2) Variablen und Methoden für die Abfrage der Größe des Charts.

7. Zudem benötigen wir eine Methode um alle weiteren Objekte im Verhältnis zu den aktualisierten Koordinaten verschieben zu können. Dafür haben wir zuvor die virtuelle Methode CWindow::Moving() deklariert. Nun müssen wir diese implementieren.

8. Zudem benötigen wir einige Hilfsmethoden, um identifizieren zu können, ob sich der Mauszeiger in dem Fenster-Kopf befindet und für das Zurücksetzen einiger Hilfsvariablen.

9. Das Verfolgen des Mauszeigers wird für alle Objekte innerhalb des Fensters funktionieren. Lassen Sie uns dafür eine Methode erzeugen, in welcher diese Überprüfung durchgeführt wird.

10. Es ist nicht immer notwendig dass ein grafisches Interface auch verschoben werden kann. Daher fügen wir noch eine Methode hinzu, die die Verschiebung aktivieren und deaktivieren kann.

 

Funktionalitäten für das Bewegen des Formulars

Lassen Sie uns nun die oben genannten Funktionalitäten implementieren. In dem Klassen-Körper fügen wir nun die Deklarationen der benötigen Methoden und einige Variablen hinzu. Beziehen Sie die Datei mit der CChart Klasse mit ein und erzeugen sie eine Instanz (Hervorgehoben in Gelb):

//+------------------------------------------------------------------+
//|                                                       Window.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include <Charts\Chart.mqh>
//+------------------------------------------------------------------+
//| Klasse für das Erzeugen einer Form mit Controls                  |
//+------------------------------------------------------------------+
class CWindow : public CElement
  {
private:
   CChart            m_chart;
   //--- Die Möglichkeit ein Fenster über den Chart bewegen zu können
   bool              m_movable;
   //--- Die Größe des Charts
   int               m_chart_width;
   int               m_chart_height;
   //--- Variablen, im Zusammenhang mit der Verschiebung
   int               m_prev_x;        // Punkt X bei Tastendruck
   int               m_prev_y;        // Punkt Y bei Tastendruck
   int               m_size_fixing_x; // Abstand von der X-Koordinate zudem Punkt X
   int               m_size_fixing_y; // Abstand von der Y-Koordinate zu dem Punkt Y
   //--- Status der Maustaste unter Berücksichtigung der Position wo er gedrückt wurde
   ENUM_WMOUSE_STATE m_clamping_area_mouse;
   //---
public:
   //--- Die Möglichkeit das Fenster zu bewegen
   bool              Movable(void)                                     const { return(m_movable);                  }
   void              Movable(const bool flag)                                { m_movable=flag;                     }
   //--- Abfragen der Größe des Charts
   void              SetWindowProperties(void);
   //--- Umwandlung der y-Koordinate in eine Relative
   void              YToRelative(const int y);
   //--- Überprüfung des Mauszeigers in dem Fenster Kopf 
   bool              CursorInsideCaption(const int x,const int y);
   //--- Zurücksetzen der Variablen
   void              ZeroPanelVariables(void);
   //--- Überprüfung des Fokus des Mauszeigers
   void              CheckMouseFocus(const int x,const int y,const int subwin);
   //--- Überprüfung des Status der linken Maustaste
   void              CheckMouseButtonState(const int x,const int y,const string state);
   //--- Festlegen des Chart Modus
   void              SetChartState(const int subwindow_number);
   //--- Aktualisieren der Koordinaten des Formulars
   void              UpdateWindowXY(const int x,const int y);
  };

Der Code für dieSetWindowProperties(), YToRelative(), CursorInsideCaption() und ZeroPanelVariables() Methoden ist sehr simpel und benötigt daher keine weitere Beschreibung. Ich möchte lediglich Ihre Aufmerksamkeit auf die Übergabe der Unterfensternummer (m_subwin) zu den Methoden des CChart Objektes richten. Dieses musst die Nummer des Unterfensters sein, in welchem das MQL Programm läuft.

//+-----------------------------------------------------------------+
//| Abfragen der Chartgröße                                         |
//+-----------------------------------------------------------------+
void CWindow::SetWindowProperties(void)
  {
//--- Abfragen der Breite und der Höhe des Chatfensters
   m_chart_width  =m_chart.WidthInPixels();
   m_chart_height =m_chart.HeightInPixels(m_subwin);
  }
//+-----------------------------------------------------------------+
//| Umwandlung der y-Koordinate in eine Relative                    |
//+-----------------------------------------------------------------+
int CWindow::YToRelative(const int y)
  {
//--- Abfrage des Abstandes von der Oberkante des Charts zu dem Indikator-Unterfenster
   int chart_y_distance=m_chart.SubwindowY(m_subwin);
//--- Umwandlung der y-Koordinate in eine Relative
   return(y-chart_y_distance);
  }
//+----------------------------------------------------------------------------------------+
//| Überprüfung, ob sich der Mauszeiger in den Bereich des Titels des Fensters befindet    |
//+----------------------------------------------------------------------------------------+
bool CWindow::CursorInsideCaption(const int x,const int y)
  {
   return(x>m_x && x<X2()-m_right_limit && y>m_y && y<m_caption_bg.Y2());
  }
//+--------------------------------------------------------------------------------------------+
//| Zurücksetzen der Variablen, die in Verbindung mit der Verschiebung des Fensters stehen und |
//| dem Status der linke Maustaste                                                             |
//+--------------------------------------------------------------------------------------------+
void CWindow::ZeroPanelVariables(void)
  {
   m_prev_x              =0;
   m_prev_y              =0;
   m_size_fixing_x       =0;
   m_size_fixing_y       =0;
   m_clamping_area_mouse =NOT_PRESSED;
  }

Die Überprüfung des Status der linken Maustaste in Relation zu der Form, findet in derCWindow::CheckMouseButtonState() Methode statt. Dazu werden die Mauszeiger-Koordinaten und der String-Parameter des Ereignismodells des Charts dieser Methode übergeben. Der String-Parameter zeigt den Status der linken Maustaste, wenn der CHARTEVENT_MOUSE_MOVE Event behandelt wird. Das bedeutet, dass dieser Parameter den Wert von "0" haben kann, wenn die Maustaste nicht gedrückt ist und "1", wenn die Maustaste gedrückt ist.

Wenn die Maustaste losgelassen wird, dann werden alle Hilfsvariablen zurückgesetzt und die Arbeit der Methode ist beendet. Wenn eine Taste gedrückt wurde, dann wird in der CWindow::CheckMouseButtonState() Methode überprüft, an welcher Stelle dieser Event aufgetreten ist. Wenn sich herausstellt, dass der Tastendruck bereits festgestellt worden ist, dann wird die Methode mit (return) abgebrochen.

//+--------------------------------------------------------------+
//| Überprüft den Status der Maustaste                           |
//+--------------------------------------------------------------+
void CWindow::CheckMouseButtonState(const int x,const int y,const string state)
  {
//--- Wenn der die Taste losgelassen wird / nicht gedrückt ist
   if(state=="0")
     {
      //--- Zurücksetzen der Variablen
      ZeroPanelVariables();
      return;
     }
//--- Falls die Taste gedrückt ist
   if(state=="1")
     {
      //--- Methode verlassen, falls dieser Zustand schon festgestellt worden ist
      if(m_clamping_area_mouse!=NOT_PRESSED)
         return;
      //--- Außerhalb des Bereichs des Panels
      if(!CElement::MouseFocus())
         m_clamping_area_mouse=PRESSED_OUTSIDE;
      //--- Innerhalb des Bereichs des Panels
      else
        {
         //--- Falls innerhalb des Bereichs des Fenster-Kopfes
         if(CursorInsideCaption(x,y))
           {
            m_clamping_area_mouse=PRESSED_INSIDE_HEADER;
            return;
           }
         //--- Falls innerhalb des Fensterbereiches
         m_clamping_area_mouse=PRESSED_INSIDE_WINDOW;
        }
     }
  }

Die Methoden für die Definition der Grenzen des Objektes, werden in der Objects.mqh Datei von allen Klassen der einfachen Objekte zur Verfügung gestellt. Diese erlauben eine schnelle und einfache Feststellung, ob sich ein Objekt in dem Fokus des Mauszeigers befindet. Der Fokus für das Formular und alle darauf enthaltenen Objekte kann in der CWindow Klass unter Verwendung der CheckMouseFocus() Methode überprüft werden. Die aktuellen Koordinaten des Mauszeigers und die Nummer des Unterfensters, in welchem sich der Mauszeiger zurzeit befindet, wird dieser Methode mit übergeben. Ein Weg für die Abfrage der Nummer des Unterfensters, in welchem sich der Mauszeiger zurzeit befindet, werden wir später aufzeigen.

//+----------------------------------------------------------------+
//| Überprüfung des Fokus des Mauszeigers                          |
//+----------------------------------------------------------------+
void CWindow::CheckMouseFocus(const int x,const int y,const int subwin)
  {
//--- Falls sich der Mauszeiger in dem Bereich des Programmfensters befindet
   if(subwin==m_subwin)
     {
      //--- Sofern wir uns nicht gerade in dem Modus für das Verschieben des Formulars befinden
      if(m_clamping_area_mouse!=PRESSED_INSIDE_HEADER)
        {
         //--- Überprüfen der Position des Mauszeigers
         CElement::MouseFocus(x>m_x && x<X2() && y>m_y && y<Y2());
         //---
         m_button_rollup.MouseFocus(x>m_button_rollup.X() && x<m_button_rollup.X2() && 
                                    y>m_button_rollup.Y() && y<m_button_rollup.Y2());
         m_button_close.MouseFocus(x>m_button_close.X() && x<m_button_close.X2() && 
                                   y>m_button_close.Y() && y<m_button_close.Y2());
         m_button_unroll.MouseFocus(x>m_button_unroll.X() && x<m_button_unroll.X2() && 
                                    y>m_button_unroll.Y() && y<m_button_unroll.Y2());
        }
     }
   else
     {
      CElement::MouseFocus(false);
     }
  }

Das Ergebnis der Prüfung des Fokus des Formulars und der Zustand der Maustaste in Bezug auf das Formular, kann angeben, ob das Scrollen und die Verwaltung der Handelsebenen deaktiviert oder aktiviert werden müssen. Falls diese Eigenschaften des Charts nicht deaktiviert werden, während sich der Mauszeiger über dem Formular befindet, dann wird ein Scrollen des Charts zusammen mit dem Verschieben und der Verwaltung der Handelsebenen unterhalb des Formulars stattfinden, was natürlich nicht gewollt ist.

//+----------------------------------------------------------------+
//| Festlegen des Status des Charts                                |
//+----------------------------------------------------------------+
void CWindow::SetChartState(const int subwindow_number)
  {
//--- Falls (der Mauszeiger befindet sich im Bereich des Panels und die Maustatste ist nicht gedrückt) oder
//    Die Maustaste wurde innerhalb des Bereichs des Formulars oder des Fenster-Kopfes gedrückt
   if((CElement::MouseFocus() && m_clamping_area_mouse==NOT_PRESSED) || 
      m_clamping_area_mouse==PRESSED_INSIDE_WINDOW ||
      m_clamping_area_mouse==PRESSED_INSIDE_HEADER)
     {
      //--- Die Aktivierung des Scrollens und das Management der Handelsebenen
      m_chart.MouseScroll(false);
      m_chart.SetInteger(CHART_DRAG_TRADE_LEVELS,false);
     }
//--- Aktivieren der Verwaltung, falls sich der Mauszeiger außerhalb des Bereiches des Fensters befindet
   else
     {
      m_chart.MouseScroll(true);
      m_chart.SetInteger(CHART_DRAG_TRADE_LEVELS,true);
     }
  }

Die Aktualisierung der Fenster Koordinaten findet in der CWindow::UpdateWindowXY() Methode statt. Am Anfang wird der Modus des Fensters überprüft. Wenn sich das Formular in einem nicht verschiebbaren Modus befindet, dann verlässt das Programm diese Methode, da es keinen Grund für eine Aktualisierung der Koordinaten gibt. Wenn die Maustaste gedrückt wurde, dann werden die aktuellen Koordinaten gespeichert, der Abstand von dem Eckpunkt des Formular zu dem Mauszeiger wird gespeichert, die Begrenzungen für das Verlassen des Chart-Bereiches werden berechnet, weitere Überprüfungen finden statt und falls notwendig, werden die Koordinaten des Formulars aktualisiert. Studieren Sie dazu den nachfolgenden Programmcode:

//+----------------------------------------------------------------+
//| Aktualisierung der Koordinaten des Fensters                    |
//+----------------------------------------------------------------+
void CWindow::UpdateWindowXY(const int x,const int y)
  {
//--- Falls der "nicht verschiebbar"-Modus gesetzt ist
   if(!m_movable)
      return;
//---
   int new_x_point =0; // New X coordinate
   int new_y_point =0; // New Y coordinate
//--- Begrenzungen
   int limit_top    =0;
   int limit_left   =0;
   int limit_bottom =0;
   int limit_right  =0;
//--- Falls die Maustaste gedrückt wurde
   if((bool)m_clamping_area_mouse)
     {
      //--- Abspeichern der XY Koordinaten des Mauszeigers
      if(m_prev_y==0 || m_prev_x==0)
        {
         m_prev_y=y;
         m_prev_x=x;
        }
      //--- Abspeichern des Abstandes von den Eckpunkt des Formulars bis zum Mauszeiger
      if(m_size_fixing_y==0 || m_size_fixing_x==0)
        {
         m_size_fixing_y=m_y-m_prev_y;
         m_size_fixing_x=m_x-m_prev_x;
        }
     }
//--- Setzen von Begrenzungen
   limit_top    =y-::fabs(m_size_fixing_y);
   limit_left   =x-::fabs(m_size_fixing_x);
   limit_bottom =m_y+m_caption_height;
   limit_right  =m_x+m_x_size;
//--- Wenn die Grenzen des Charts nicht überschritten werden (oben, unten,rechts und links)
   if(limit_bottom<m_chart_height && limit_top>=0 && 
      limit_right<m_chart_width && limit_left>=0)
     {
      new_y_point =y+m_size_fixing_y;
      new_x_point =x+m_size_fixing_x;
     }
//--- Falls die ganzen bis Charts überschritten werden
   else
     {
      if(limit_bottom>m_chart_height) // > downwards
        {
         new_y_point =m_chart_height-m_caption_height;
         new_x_point =x+m_size_fixing_x;
        }
      if(limit_top<0) // > upwards
        {
         new_y_point =0;
         new_x_point =x+m_size_fixing_x;
        }
      if(limit_right>m_chart_width) // > right
        {
         new_x_point =m_chart_width-m_x_size;
         new_y_point =y+m_size_fixing_y;
        }
      if(limit_left<0) // > left
        {
         new_x_point =0;
         new_y_point =y+m_size_fixing_y;
        }
     }
//--- Eine Aktualisierung der Koordinaten, falls eine Verschiebung stattgefunden hat
   if(new_x_point>0 || new_y_point>0)
     {
      //--- Justierung der Formular-Koordinaten
      m_x =(new_x_point<=0)? 1 : new_x_point;
      m_y =(new_y_point<=0)? 1 : new_y_point;
      //---
      if(new_x_point>0)
         m_x=(m_x>m_chart_width-m_x_size-1) ? m_chart_width-m_x_size-1 : m_x;
      if(new_y_point>0)
         m_y=(m_y>m_chart_height-m_caption_height-1) ? m_chart_height-m_caption_height-2 : m_y;
      //--- Zurücksetzen der festen Punkte
      m_prev_x=0;
      m_prev_y=0;
     }
  }

In dem OnEvent() Chart-Eventhandler der CWindow Klasse, wo das Event für Mausbewegungen behandelt wird (CHARTEVENT_MOUSE_MOVE), kann die Nummer des Unterfensters mit der ChartXYToTimePrice() Function abgefragt werden. Falls die Funktion true zurückgibt, Dann erhalten wir über die CWindow::YToRelative() Methode eine relative y-Koordinate und über die anschließende Iteration der oben genannten Methoden:

//+-----------------------------------------------------------------+
//| Chart Eventhandler                                              |
//+-----------------------------------------------------------------+
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      int      x      =(int)lparam; // Koordinate der x-Achse
      int      y      =(int)dparam; // Koordinate der y-Achse
      int      subwin =WRONG_VALUE; // Die Nummer des Fensters, in welchem sich der Mauszeiger befindet
      datetime time   =NULL;        // Die Zeit, entsprechend der x-Koordinate
      double   level  =0.0;         // Level (Kurs) Entsprechend der y-Koordinate
      int      rel_y  =0;           // Für die Identifikation der relativen y-Koordinate
      //--- Abfrage der Position des Mauszeigers
      if(!::ChartXYToTimePrice(m_chart_id,x,y,subwin,time,level))
         return;
      //--- Abfrage der relativen y-Koordinate
      rel_y=YToRelative(y);
      //--- Überprüfung und Abspeichern des Status der Maustaste
      CheckMouseButtonState(x,rel_y,sparam);
      //--- Überprüfung des Fokus des Mauszeigers
      CheckMouseFocus(x,rel_y,subwin);
      //--- Setzen des Status des Charts
      SetChartState(subwin);
      //--- Falls die Verwaltung auf das Fenster gerichtet ist, Identifizierung der Position
      if(m_clamping_area_mouse==PRESSED_INSIDE_HEADER)
        {
         //--- Aktualisierung der Fenster-Koordinaten
         UpdateWindowXY(x,rel_y);
        }
      return;
     }
  }

Um die Verschiebung testen zu können, muss nur noch die CWindow::Moving() Methode implementiert werden. Es sieht jetzt danach aus, dass diese Methode in dem internen Eventhandler der Klasse, direkt nach der UpdateWindowXY() Methode aufgerufen werden muss, aber dieses ist nicht der Fall. Wenn wir dieses jetzt, wenn es noch keine weiteren Controls in der Form gibt machen würden, dann wäre das Ergebnis einwandfrei. Wenn man dieser Logik folgt, dann wäre es notwendig, das selbe Vorgehen in allen anderen Klassen mit allen Controls durchzuführen. Das Ergebnis wäre, wenn man eine Form mit sehr vielen hinzugefügten Controls hat und diese bewegt, dass alle Controls zusammen mit der Form, aber mit einer Verzögerung bewegt werden. Und dieses wäre ein schlechtes Ergebnis.

Der Grund dafür ist, dass dann jedes Control seine eigenen Überprüfungen für eine eventuelle Verschiebung vornimmt. Dieses verursacht dann die Verzögerung. Damit sich alle Controls synchron bewegen, darf es keine weiteren Operationen zwischen den Moving() Methoden geben. Dieses kann mit Hilfe der CWndEvents Klasse durchgeführt werden, da sie eine abgeleitete Klasse von der CWndContainer Klasse ist, die einen direkten Zugriff zu allen Objekt-Pointern besitzt, die hier abgespeichert sind.

Der Inhalt der CWindow::Moving() Methode besteht aus zwei Teilen. Zunächst werden die Koordinaten aller Objekte, die zu dieser Form gehören, abgespeichert und dann werden die Objektkoordinaten aktualisiert. Später werden wir die Moving() Methode in jedem Control einfügen.

//+----------------------------------------------------------------+
//| Verschieben des Fensters                                       |
//+----------------------------------------------------------------+
void CWindow::Moving(const int x,const int y)
  {
//--- Abspeichern der Koordinaten in Variabeln
   m_bg.X(x);
   m_bg.Y(y);
   m_caption_bg.X(x);
   m_caption_bg.Y(y);
   m_icon.X(x+m_icon.XGap());
   m_icon.Y(y+m_icon.YGap());
   m_label.X(x+m_label.XGap());
   m_label.Y(y+m_label.YGap());
   m_button_close.X(x+m_button_close.XGap());
   m_button_close.Y(y+m_button_close.YGap());
   m_button_unroll.X(x+m_button_unroll.XGap());
   m_button_unroll.Y(y+m_button_unroll.YGap());
   m_button_rollup.X(x+m_button_rollup.XGap());
   m_button_rollup.Y(y+m_button_rollup.YGap());
   m_button_tooltip.X(x+m_button_tooltip.XGap());
   m_button_tooltip.Y(y+m_button_tooltip.YGap());
//--- Aktualisieren der Koordinaten in den grafischen Objekten
   m_bg.X_Distance(m_bg.X());
   m_bg.Y_Distance(m_bg.Y());
   m_caption_bg.X_Distance(m_caption_bg.X());
   m_caption_bg.Y_Distance(m_caption_bg.Y());
   m_icon.X_Distance(m_icon.X());
   m_icon.Y_Distance(m_icon.Y());
   m_label.X_Distance(m_label.X());
   m_label.Y_Distance(m_label.Y());
   m_button_close.X_Distance(m_button_close.X());
   m_button_close.Y_Distance(m_button_close.Y());
   m_button_unroll.X_Distance(m_button_unroll.X());
   m_button_unroll.Y_Distance(m_button_unroll.Y());
   m_button_rollup.X_Distance(m_button_rollup.X());
   m_button_rollup.Y_Distance(m_button_rollup.Y());
   m_button_tooltip.X_Distance(m_button_tooltip.X());
   m_button_tooltip.Y_Distance(m_button_tooltip.Y());
  }

 

Test der Bewegbarkeit des Formulars über den Chart

Alle Events müssen in der CWndEvents::ChartEvent() Methode behandelt werden, welche zuvor schon erzeugt wurde aber noch leer ist. Zunächst wird eine Überprüfung der Größe des Fenster-Pointer-Arrays durchgeführt. Falls dieses leer ist, dann gibt es keinen Punkt der bearbeitet werden müsste und das Programm verlässt die Methode (return). Dann werden die Klassenfelder, die sich auf die Parameter der Chart-Ereignisse beziehen, initialisiert. Wir werden zunächst zwei Funktionen für das Behandeln von Events einbauen: (1) Für die Überprüfung von Events in den Eventhandlern von jedem Control CWndEvents::CheckElementsEvents() und (2) für das verfolgen des Mauszeigers CWndEvents::ChartEventMouseMove().

Somit benötigt die CWndEvents::ChartEvent() Methode den nachfolgend aufgeführten Code:

//+------------------------------------------------------------------+
//| Programm Eventhandling                                           |
//+------------------------------------------------------------------+
void CWndEvents::ChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Falls das Array leer ist, Abbruch
   if(CWndContainer::WindowsTotal()<1)
      return;
//--- Initialisierung der Felder der Eventparameter
   InitChartEventsParams(id,lparam,dparam,sparam);
//--- Überprüfung der Events der Interface-Controls
   CheckElementsEvents();
//--- Event für die Bewegung des Mauszeigers
   ChartEventMouseMove();
  }

Implementierung der CWndEvents::CheckElementsEvents() und CWndEvents::ChartEventMouseMove() Methoden, da sie zurzeit noch leer sind. Das Überprüfen der Ereignisse der Schnittstellensteuerungen wird in einem Zyklus durchgeführt. Wir rufen sequentiell alle OnEvent() Handler aller Controls, die sich in der Basis befinden, auf. Da wir zur Zeit nur ein Fenster haben, benutzen wir temporär den Null-Index des Fenster-Arrays in der CWndEvents Klasse. Dieses muss natürlich wieder geändert werden, sobald wir einen Multi-Fenster-Modus entwickeln.

//+----------------------------------------------------------------+
//| Verifizierung der Control Events                               |
//+----------------------------------------------------------------+
void CWndEvents::CheckElementsEvents(void)
  {
   int elements_total=CWndContainer::ElementsTotal(0);
   for(int e=0; e<elements_total; e++)
      m_wnd[0].m_elements[e].OnEvent(m_id,m_lparam,m_dparam,m_sparam);
  }

Lassen wir uns eine Methode in der CWndEvents Klasse erzeugen, mit welcher die Bewegung über den Chart durchgeführt wird. Wir nennen sie MovingWindow(). In dieser Methode wird zunächst die Bewegung des Fensters ausgeführt und anschließend die Bewegung aller Controls, die diesem Fenster hinzugefügt worden sind.

class CWndEvents : public CWndContainer
  {
private:
   //--- Verschieben des Fensters
   void              MovingWindow(void);
  };
//+----------------------------------------------------------------+
//| Verschieben des Fensters                                       |
//+----------------------------------------------------------------+
void CWndEvents::MovingWindow(void)
  {
//--- Verschieben des Fensters
   int x=m_windows[0].X();
   int y=m_windows[0].Y();
   m_windows[0].Moving(x,y);
//--- Verschieben der Controls
   int elements_total=CWndContainer::ElementsTotal(0);
   for(int e=0; e<elements_total; e++)
      m_wnd[0].m_elements[e].Moving(x,y);
  }

Jetzt kann der Programmcode der CWndEvents::ChartEventMouseMove() Methode wie folgt hinzugefügt werden. Vergessen Sie nicht nach jeder Aktualisierung der Koordinaten den Chart neu zu zeichnen (redraw), ansonsten wenn Ihre Änderungen nicht übernommen.

//+----------------------------------------------------------------+
//| CHARTEVENT MOUSE MOVE event                                    |
//+----------------------------------------------------------------+
void CWndEvents::ChartEventMouseMove(void)
  {
//--- Abbruch, falls es sich nicht um ein Event für eine neue Position des Mauszeigers hält
   if(m_id!=CHARTEVENT_MOUSE_MOVE)
      return;
//--- Verschieben des Fensters
   MovingWindow();
//--- Neuzeichnen des Charts
   m_chart.Redraw();
  }

Wir befinden uns nun in der letzten Phase der Entwicklung, wo wir einen Test für das Verschieben eines Fensters im Chart durchführen können. Fügen Sie in dem Körper der CWindow::OnEvent() Methode den folgenden Programmcode für die Anzeige von Daten in der linken oberen Ecke des Charts ein, wo in Echtzeit das folgende dargestellt werden soll:

Dieser Programmcode und kann nach dem Test wieder entfernt werden.

::Comment("x: ",x,"\n",
                "y: ",y,"\n",
                "rel_y: ",rel_y,"\n",
                "w.x: ",m_x,"\n",
                "w.y: ",m_y,"\n",
                "subwin: ",subwin,"\n",
                "m_subwin: ",m_subwin,"\n",
                "clamping mode: ",m_clamping_area_mouse);

Kompilieren Sie die Projektdateien und laden Sie das Programm auf einem Chart. Um zu überprüfen, ob das Programm die Position des Mauszeigers korrekt verfolgt, laden Sie noch einen Indikator in den Chart, welcher nicht in dem Haupt-Chartfenster dargestellt wird.

Ich bin mir nahezu sicher, dass es Ihnen nicht möglich sein wird, das Fenster zu bewegen, nachdem es auf den Chart geladen worden ist. Der Grund dafür ist, dass in dem Konstruktor der CWindow Klasse die m_movable Variable mit dem Wert false initialisiert wird, was natürlich bedeutet dass der Anwender das Formular nicht bewegen kann. Aus diesem Grunde muss der Entwickler der MQL Anwendung noch angeben, ob das Formular bewegbar ist oder nicht.

Fügen Sie noch folgende Programmzeile in der in der CProgram Klasse der CreateWindow() Methode ein, Kompilieren Sie die Dateien und versuchen Sie erneut diese Anwendung zu testen.

//+----------------------------------------------------------------+
//| Erzeugung einer Form für Controls                              |
//+----------------------------------------------------------------+
bool CProgram::CreateWindow(const string caption_text)
  {
//--- Hinzufügen eines Pointers des Fensters zu dem Fenster-Array
   CWndContainer::AddWindow(m_window);
//--- Koordinaten
   int x=1;
   int y=1;
//--- Eigenschaften
   m_window.Movable(true);
   m_window.XSize(200);
   m_window.YSize(200);
//--- Erzegen der Form
   if(!m_window.CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
//---
   return(true);
  }

Nun sollte es nichts geben was das Formular an einer Bewegung hindern kann:

Abbildung  1. Test für das Verschieben des Formulars auf dem Chart.

Abbildung 1. Test der Bewegbarkeit des Formulars über den Chart

Manchmal passiert es, dass die Größe des Chartfensters geändert werden muss. In diesen Momenten tritt das CHARTEVENT_CHART_CHANGE Ereignis auf. Zurzeit wird dieses noch nicht geprüft, bzw behandelt und es kann daher eine Situation auftreten, wo das Formular partiell oder komplett außerhalb der Grenzen des Chartfensters liegt. Um dieses zu verhindern, müssen wir dieses Events in dem CWindow::OnEvent() Chart-Eventhandler überprüfen.

Da dieses Event auch beim Scrollen eines Charts auftritt, müssen wir den Status der linken Maustaste überprüfen, damit nicht unnötig viele Aktionen durchgeführt werden. Wenn die linke Maustaste losgelassen wird, erhalten wir die Größe des Charts und falls die Begrenzungen des Charts überschritten werden, dann aktualisieren wir die Koordinaten. Nachfolgend finden Sie den Programmcode, der an dem Ende der CWindow::OnEvent() Methode angefügt werden muss:

//+----------------------------------------------------------------+
//| Chart Eventhandler                                             |
//+----------------------------------------------------------------+
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Events bei einer Veränderung der Chart-Eigenschaften
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- Wenn der die Taste losgelassen wird / nicht gedrückt ist
      if(m_clamping_area_mouse==NOT_PRESSED)
        {
         //--- Abfrage der Größe des Chartfensters
         SetWindowProperties();
         //--- Anpassung der Koordinaten
         UpdateWindowXY(m_x,m_y);
        }
      return;
     }
  }

Die oben beschriebene Arbeit ergibt nun die Möglichkeit ein Formular mit Steuerelementen verschieben zu können. Jetzt müssen wir noch die Funktionalität für das minimieren und maximieren bisfans das Einbetten. Zudem sollen die Buttons auch auf Mausbewegungen reagieren, damit der Anwender auch versteht, dass dieses verwendbare Icons sind und nicht nur Designelemente.

 

Verändern des Aussehens der Interface-Komponente, sobald sich die Maus darüber befindet.

Zuvor haben wir schon die Implementation der CElement Klasse btrachtet, welches die Basisklasse für alle Controls ist. Wir haben die CElement::ChangeObjectColor() Methode für die Veränderung der Farbe erzeugt, sobald sich der Mauszeiger über dem Objekt oder einem seiner Elemente befindet. Jetzt kommt der Zeitpunkt, wo wir diesen Mechanismus für unsere Arbeit verwenden können. Diese Funktionalität benötigt einen Timer. Dieser ist in den Einstellungen der MQL Anwendung standardmäßig deaktiviert. Es bleibt dem Anwender, beziehungsweise dem Entwickler einer Anwendung überlassen, ob er den Timer verwendet oder nicht.

Für die Aktivierung des Timers, besitzt die Programmiersprache MQL zwei Funktionen mit unterschiedlichen Frequenzen: EventSetTimer() und EventSetMillisecondTimer(). Die erste ist für Intervalle gedacht, die nicht kleiner als eine Sekunde sind. Dieses ist für unsere Zwecke aber nicht passend, da ein Intervall von einer Sekunde zu langsam ist, um das Erscheinungsbild eines Controls zu verändern, sobald sich der Mauszeiger darüber befindet. Der Wechsel sollte sofort passieren. Daher verwenden wir die EventSetMillisecondTimer() Funktion, welcher einen Timer mit Intervallen in Millisekunden unterstützt. Gemäß der MQL Referenz, sollte das kleinste Intervall die Dauer von 10 - 16 Millisekunden nicht unterschreiten. Dieses ist für unsere Zwecke aber ausreichend.

Wir fügen dazu eine Konstante für den Intervall des Timers in unserer Bibliothek in der Datei Defines.mqh hinzu:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//--- Timer step (milliseconds)
#define TIMER_STEP_MSC (16)

Eventuell fragen Sie sich gerade folgendes "Ist diese Frequenz nicht zu hoch?" und "Wird dieses nicht sehr viele Ressourcen verbrauchen?". Wir beantworten diese Fragen in dem Moment, wo wir unsere Idee testen.

Aktivieren Sie den Timer in der CWndEvents Klasse, welche den Kern unserer Bibliothek darstellt. Zu diesem Zweck fügen Sie bitte die nachfolgenden Programmzeilen in den Konstruktor und Destruktor ein:

//+------------------------------------------------------------------+
//| Konstruktor                                                      |
//+------------------------------------------------------------------+
CWndEvents::CWndEvents(void)
  {
//--- Aktivierung des Timers
   if(!::MQLInfoInteger(MQL_TESTER))
      ::EventSetMillisecondTimer(TIMER_STEP_MSC);
  }
//+------------------------------------------------------------------+
//| Destruktor                                                       |
//+------------------------------------------------------------------+
CWndEvents::~CWndEvents(void)
  {
//--- Entfernen des Timers
   ::EventKillTimer();
  }

In dem Konstruktor der CWndEvents Klasse, wird der Timer nur aktiviert, falls sich das Programm außerhalb des Strategietesters befindet. Unser Bibliothek funktioniert in Moment mit dem Strategietester nicht, da dieser einige Einschränkungen für das Arbeiten mit grafischen Objekten besitzt. Es sei noch erwähnt, dass das kleinste Intervall, welches in dem Strategietester angewendet werden kann, eine Sekunde beträgt, auch wenn man die Funktion EventSetMillisecondTimer() verwendet. In den distruptor wird der Timer mit der Funktion EventKillTimer() deaktiviert.

In den Klassen der Controls gibt es jeweils eine eigene Implementation der OnEventTimer() Methode, und daher gibt es in der CElement Klasse eine virtuelle Methode mit diesem Namen. Ähnlich wie bei der CWndEvents::CheckElementsEvents() Methode, wo ein Durchlaufen der OnEvent() Methoden jedes Controls erfolgt, müssen wir auch eine Methode erzeugen, in welcher das Programm alle OnEventTimer() Methoden durchläuft. Lassen Sie sie uns CheckElementsEventsTimer() nennen.

Declaration und Implementation der CWndEvents::CheckElementsEventsTimer() Methode:

class CWndEvents : public CWndContainer
  {
private:
   //--- Überprüfen der Events von allen Controls über einen Timer
   void              CheckElementsEventsTimer(void);
  };
//+-----------------------------------------------------------------+
//| Überprüfen alle Events von allen Controls durch einen Timer     |
//+-----------------------------------------------------------------+
void CWndEvents::CheckElementsEventsTimer(void)
  {
   int elements_total=CWndContainer::ElementsTotal(0);
   for(int e=0; e<elements_total; e++)
      m_wnd[0].m_elements[e].OnEventTimer();
  }

Anschließend muss diese Methode in der CWndEvents::OnTimerEvent() Methode mit der Überprüfung der Größe des Fenster-Arrays ganz am Anfang der CWndEvents::ChartEvent() Methode durchgeführt werden.

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

In der CProgram Klasse, in welcher der Entwickler der MQL Anwendung das graphische Interface erzeugt, muss in der CProgram::OnTimerEvent() Methode, welchen mit der Hauptprogrammdatei verbunden ist, die gleichnamige Methode der Basisklasse aufgerufen werden:

//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CProgram::OnTimerEvent(void)
  {
   CWndEvents::OnTimerEvent();
  }

Auf diesem Weg sind alle Aktionen von allen Controls, welche sich in der OnEventTimer() Methode befinden, in dem Event des Programm-Timers zugreifbar.

Noch sind wir nicht so weit, den Test Ausführen zu können. Die Bibliothek, welchen wir zur Zeit entwickeln, behinhaltet nur eine Interface-Komponente — eine Form mit Controls, die CWindow Klasse. Wir müssen noch eine notwendige Funktionalität hinzufügen, welche der lokalen Version der OnEventTimer() Methode eingefügt werden muss.

Wir nennen die Änderung der Farbe CWindow::ChangeObjectsColor(). Nachfolgend finden Sie die Deklaration und Implementation in der CWindow Klasse:

class CWindow: public CElement
  {
private:
   //--- Die Veränderung der Farbe der Objekte des Formulars
   void              ChangeObjectsColor(void);
  };
//+---------------------------------------------------------------------------+
//| Die Veränderung der Farbe, sobald sich der Mauszeiger darüber befindet    |
//+---------------------------------------------------------------------------+
void CWindow::ChangeObjectsColor(void)
  {
//--- Die Veränderung der Icons in den Buttons
   m_button_rollup.State(m_button_rollup.MouseFocus());
   m_button_unroll.State(m_button_unroll.MouseFocus());
   m_button_close.State(m_button_close.MouseFocus());
//--- Die Veränderung der Farbe in dem Fenster-Kopf
   CElement::ChangeObjectColor(m_caption_bg.Name(),CElement::MouseFocus(),OBJPROP_BGCOLOR,
                               m_caption_bg_color,m_caption_bg_color_hover,m_caption_color_bg_array);
  }

Wie Sie den Code entnehmen können, gibt es keine komplizierten Befehle in der CWindow::ChangeObjectsColor() Methode. Die MouseFocus() gibt den Fokus des Mauszeigers über alle Objekte zurück, was in der CWindow::CheckMouseFocus() Methode überprüft wird, wenn sich der Mauszeiger über den Chart bewegt. Zuvor haben wir zwei Icons in die Objekte geladen, welche die Rolle von Buttons übernehmen. Sie können über den Status mit der Methode CChartObjectBmpLabel::State() selektiert werden. Die CElement::ChangeObjectColor() Methode arbeitet mit der Farbe des angegebenen Teils eines Objektes.

In diesem Fall, verändert sich die Farbe des Hintergrundes der Kopfzeile, wenn sich der Mauszeiger innerhalb des Bereichs des Fensters befindet. Es kann auch die Farbe des Rahmens der Kopfzeilen oder der Hintergrund zusammen mit dem Rahmen oder die Farbe von anderen Objekten geändert werden, wenn die CElement::ChangeObjectColor() Methode sequentiell mit dem Namen des Objektes in dem ersten Parameter aufgerufen wird.

Das einzige was jetzt noch fehlt, ist ein Aufruf dieser Methode in dem lokalen Timer:

//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CWindow::OnEventTimer(void)
  {
//--- Die Veränderung der Farbe der Objekte des Formulars
   ChangeObjectsColor();
  }

Kompilieren Sie alle Dateien die sich geändert haben neu und laden Sie das Programm für einen Test auf den Chart. Wenn Sie jetzt den Mauszeiger über ein Objekt bewegen, welches eine implementierte Funktion besitzt, dann wird sich die Farbe dieses Objektes ändern.

Abbildung  2. Test der Reaktion eines Objektes, wenn sich der Mauszeiger darüber bewegt.

Abbildung 2. Test der Reaktion eines Objektes, wenn sich der Mauszeiger darüber bewegt.

 

Schlussfolgerung

In diesem Artikel haben wir mit den Erweiterungen unsers Programmcodes die Möglichkeut geschaffen, dass man eine Form über den Chart bewegen kann. Die Controls auf diesem Formular reagieren nun auf die Bewegungen des Mauszeigers. In den nächsten Artikel werden wir die CWindow Klasse weiter entwickeln. Sie wird mit zusätzlichen Methoden ausgestattet, welche es dann erlauben, die Form über das Klicken auf deren Controls zu verwalten.

Sie können das gesamte Material des ersten Teils dieser Serie auf Ihren Rechner herunterladen und testen. Wenn Sie fragen zur Verwendung dieses Materials haben, dann können Sie zunächst auf die detaillierte Beschreibung in dem Artikel zu dieser Bibliothek zurückgreifen oder Sie stellen Ihre Frage(n) in den Kommentaren zu diesem Artikel.

Liste der Artikeln (Kapitel) des ersten Teils: