Grafische Interfaces I: Funktionen für die Form-Buttons und das Entfernen der Interface Elemente (Kapitel 4)

Anatoli Kazharski | 31 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 vorangegangenen Artikel, haben wir die CWindow Klasse um Funktionen erweitert, die es uns ermöglicht haben das Formular über den Chart zu bewegen. Zudem reagieren die Controls der Form auf die Bewegungen des Mauszeigers. In diesem Artikel werden wir mit der Entwicklung der CWindow Klasse Fortschreiten, indem wir ihr Methoden hinzufügen, welche es uns erlauben das Formular über das Anklicken seiner Controls zu verwalten. Wir sind dann in der Lage das Formular über einen Button zu schließen, so wie es zu minimieren und maximieren.

 

Funktionen für die Formular-Buttons

Die Form aus der Bibliothek, die wir gerade entwickeln, beinhaltet zwei wichtige Buttons, die für einen EA und Indikator obligatorisch sind und einen zusätzlichen Button, der möglicherweise nicht angezeigt wird.

  1. Der erste Button auf der rechten Seite besitzt die Funktion um das Fenster zu schließen. Hier wird nun erklärt, wie dieses umgesetzt wird. Wenn der Button, auf den geclickt worden ist, zu einem Dialogfenster gehört, dass aus dem Hauptfenster oder einem anderen Dialogfenster aufgerufen wurde, dann muss dieses Dialogfenster geschlossen werden. Wenn der Button zu einem Hauptfenster gehört, dann wird dieses Programm aus dem Chart gelöscht.

  2. Der zweite Button besteht aus zwei Teilen: Um genau zu sein, handelt es sich hier um zwei Buttons. Je nach aktuellem Modus, wird einer der beiden Buttons dargestellt. Wenn das Fenster maximiert ist, dann wird der Button für das Minimieren des Fensters angezeigt. Und ein minimiertes Fenster hat natürlich einen Button um es zu maximieren.

  3. Der dritte Button wurde dazu entwickelt, den benutzerdefinierten Tooltip-Mode zu aktivieren. Hier noch eine genauere Erklärung, was "benutzerdefinierter Tooltip" bedeutet. Die MQL Programmiersprache, erlaubt es, dass Tooltips zu jedem grafischen Objekt angezeigt werden können, über welchen sich der Mauszeiger gerade befindet.

    Sie haben sicher bemerkt, dass in dem Programmcode der CWindow Klasse die CChartObject::Tooltip() Methode für das Erzeugen von Objekten auf dem Formular verwendet worden ist, wenn es darum ging die Eigenschaften des Objektes zu spezifizieren. Wenn ein Objekt ein Tooltip erhalten soll, dann muss der entsprechende Text, der angezeigt werden soll wenn sich der Mauszeiger über dem Objekt befindet, in der Methode Tooltip() angegeben werden. Wenn für dieses Objekt kein Tooltip benötigt wird, dann müssen sie "\n" der Methode übergeben.

    Diese Eigenschaft hat einige Einschränkungen: (1) Wenn der Text länger als 128 Zeichen ist, dann kann er nicht komplett angezeigt werden und (2) Texteigenschaften wie Farbe, Zeichensatz etc, können nicht verwaltet werden. Somit sind dieses eigentlich keine benutzerdefinierten Tooltips. Wir bezeichnen sie als Standard MQL Tooltips. Solche Tooltips eignen sich gut für kurze Kommentare. Sie lassen sich jedoch nicht verwenden, wenn der Text deutlich größer, er aus einigen Zeilen bestehen und eventuell auch ein paar Wörter hervorgehoben sein sollen.

    Ich bin mir sicher, dass fast jeder mit Excel vertraut ist. In dem nachfolgenden Screenshot sehen Sie, wie ein Tooltip mit allen möglichen Optionen aussehen kann. Wir werden unsere Bibliothek, die wir gerade entwickeln, eine entsprechende Klasse für das Anzeigen von Tooltips hinzufügen. Diese können wir dann als benutzerdefinierte Tooltips bezeichnen, da wir alle Funktionalitäten selbst entwickeln. Wir kommen auf diese Angelegenheit zurück, sobald wir die Formularklasse für Controls komplett implementiert und mindestens ein Control einer Form hinzugefügt haben.

Abbildung  1. Tooltips in Excel

Abbildung 1. Tooltips in Excel

Lassen Sie uns mit den Button für das Schließen des Fensters beginnen. Erzeugen Sie in der CWindow Klasse die Methode CloseWindow(). Sie wird innerhalb des CWindow::OnEvent() Chart-Eventhandlers aufgerufen, wenn ein CHARTEVENT_OBJECT_CLICK Event behandelt werden soll. Dieser Event wird auf, sobald ein grafisches Objekt mit der Maus angeklickt wurde. In diesem Moment enthält der (sparam) den Namen des Objektes, der mit der Maustaste angeklickt worden ist. Aus diesem Grunde muss an dieser Stelle überprüft werden, ob der Name des Objektes dem gewünschten Objekt entspricht.

Die CWindow::CloseWindow() Methode akzeptiert nur einen Parameter — den Namen des Objektes, welches angeklickt wurde. Am Anfang des Codes wird geprüft, ob der Klick auf das Objekt unserer Schaltfläche zum Schließen des Fensters entspricht. Anschließend teilt sich der Code in zwei Bereiche auf - für das Hauptfenster und für das Dialogfenster. Da unser Multi-Fenster-Modus noch nicht vollständig ist, ist die Verzweigung zu dem Dialogfenster zur Zeit noch leer. Wir werden dieses später implementieren. Nun ist alles fertig um mit dem Hauptfenster arbeiten zu können. Jetzt muss noch der Typ des Programms überprüft werden. (ob es sich um eine EA oder einen Indikator handelt) Dies ist notwendig, da das Entfernen von unterschiedlichen Typen von Programmen auch unterschiedliche Funktionen in der MQL Programmiersprache erfordert.

MQL hat seine eigenen Funktionen bei dem Aufruf eines Dialogfensters, wenn der Benutzer verschiedene Aktionen bestätigen soll. Dieses ist die MessageBox() Funktion. Wir benutzen dieses temporär, um die Entfernung eines EA vom Chart bestätigen zu lassen. Dieses Fenster kann jedoch nicht für einen Indikator verwendet werden, da es sich hier um ein Fenster vom Typ "Modal" handelt. Der Grund dafür ist, dass ein Indikator im Hauptthread ausgeführt wird, der nicht unterbrochen werden sollte. Wenn der Multi-Fenster-Modus und die Button-Steuerung implementiert ist, können wir unser Dialogfenster verwenden.

Bevor ein Programm vom Chart entfernt wird, wird noch eine Nachricht in dem Journal des Terminals geschrieben, dass das Programm auf Wunsch des Users entfernt wurde.

Lassen Sie uns folgende Ergänzungen in der CWindow Klasse vornehmen.

Deklaration und Implementation der CWindow::CloseWindow() Methode für das Schließen eines Fensters:

class CWindow: public CElement
  {
public:
   //--- Schließen des Fensters
   bool              CloseWindow(const string pressed_object);
  };
//+----------------------------------------------------------------+
//| Schließen des Dialogfensters oder des Programms                |
//+----------------------------------------------------------------+
bool CWindow::CloseWindow(const string pressed_object)
  {
//--- Wenn nicht der Button für das Schließen des Fensters angeklickt wurde
   if(pressed_object!=m_button_close.Name())
      return(false);
//--- Falls es sich um das Hauptfenster handelt
   if(m_window_type==W_MAIN)
     {
      //--- Falls es sich um ein Programm des Typs "Expert Advisor" handelt
      if(CElement::ProgramType()==PROGRAM_EXPERT)
        {
         string text="Do you want the program to be deleted from the chart?";
         //--- Öffnen eines Dialogfensters
         int mb_res=::MessageBox(text,NULL,MB_YESNO|MB_ICONQUESTION);
         //--- Falls der Button "Yes" angeklickt wurde, wird das Programm vom Chart entfernt
         if(mb_res==IDYES)
           {
            ::Print(__FUNCTION__," > The program was deleted from the chart due to your decision!");
            //--- Entfernen des Expert-Advisors vom Chart
            ::ExpertRemove();
            return(true);
           }
        }
      //--- Falls das Programm vom Typ "Indicator" ist
      else if(CElement::ProgramType()==PROGRAM_INDICATOR)
        {
         //--- Entfernen des Indikator vom Chart
         if(::ChartIndicatorDelete(m_chart_id,m_subwin,CElement::ProgramName()))
           {
            ::Print(__FUNCTION__," > The program was deleted from the chart due to your decision!");
            return(true);
           }
        }
     }
   //--- Falls es sich um ein Dialogfenster handelt
   else if(m_window_type==W_DIALOG)
     {
     }
//---
   return(false);
  }

Aufruf der CWindow::CloseWindow() Methode in dem Chart-Eventhandler:

//+-----------------------------------------------------------------+
//| Chart Eventhandler                                              |
//+-----------------------------------------------------------------+
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Behandeln des Objekt-Click Events
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Schließen des Fensters
      CloseWindow(sparam);
      return;
     }
  }

Kompilieren Sie die Dateien der Bibliothek und des EAs, welchen wir für die Tests benutzen. Laden Sie den EA auf einen Chart Wenn Sie nun den Close-Button anklicken, erhalten Sie ein neues Fenster, wie es in dem Screenshot dargestellt ist:

Abbildung  2. Test für das Schließen des Programms durch das Anklicken eines Buttons

Abbildung 2. Test für das Schließen des Programms durch das Anklicken eines Buttons

Nun erzeugen wir die Methoden für den zweiten Button, welche es erlaubt das Fenster zu minimieren und zu maximieren:

Diese Methoden sind sehr einfach und auch sehr ähnlich in ihrem Inhalt. Am Anfang der Methode wird zunächst das Icon ausgetauscht, anschließend wird die Größe (Höhe) des Hintergrundes verändert und gespeichert, danach wird der Fokus zurückgesetzt und ein Status nach den Wünschen des Benutzers gesetzt. Falls es sich bei dem Programm um einen Indikator handelt, der sich in irgendeinem anderen Fenster als dem Hauptfenster befindet, dann wird in dem Augenblick der Erzeugung des Formulars der Modus für die festgelegte Höhe des Indikator-Unterfensters gesetzt. Wenn der Modus für eine Veränderung der Größe des Unterfensters gesetzt wurde, dann wird die CWindow::ChangeSubwindowHeight() Methode aufgerufen. Diese haben wir zuvor schon erzeugt und sie befindet sich in der CWindow Klasse.

Der Programmcode der CWindow::RollUp() und CWindow::Unroll() Methoden wird hier nun in detail beschrieben:

//+----------------------------------------------------------------+
//| Minimierung des Fensters                                       |
//+----------------------------------------------------------------+
void CWindow::RollUp(void)
  {
//--- Wechseln des Buttons
   m_button_rollup.Timeframes(OBJ_NO_PERIODS);
   m_button_unroll.Timeframes(OBJ_ALL_PERIODS);
//--- Setzen und speichern der Größe
   m_bg.Y_Size(m_caption_height);
   CElement::YSize(m_caption_height);
//--- Deaktivieren des Buttons
   m_button_unroll.MouseFocus(false);
   m_button_unroll.State(false);
//--- Status des Formulars "Minimiert"
   m_is_minimized=true;
//--- Falls es sich um einen Indikator mit einer festgelegten Höhe und Breite handelt, dann setzt der Unterfenster Minimierungs-Modus
//    die Größe des Indikator Unterfensters
   if(m_height_subwindow_mode)
      if(m_rollup_subwindow_mode)
         ChangeSubwindowHeight(m_caption_height+3);
  }
//+-----------------------------------------------------------------+
//| Maximierung des Fensters                                        |
//+-----------------------------------------------------------------+
void CWindow::Unroll(void)
  {
//--- Wechseln des Buttons
   m_button_unroll.Timeframes(OBJ_NO_PERIODS);
   m_button_rollup.Timeframes(OBJ_ALL_PERIODS);
//--- Setzen und speichern der Größe
   m_bg.Y_Size(m_bg_full_height);
   CElement::YSize(m_bg_full_height);
//--- Deaktivieren des Buttons
   m_button_rollup.MouseFocus(false);
   m_button_rollup.State(false);
//--- Status des Formulars "Maximiert"
   m_is_minimized=false;
//--- Falls es sich um einen Indikator mit einer festgelegten Höhe und Breite handelt, dann setzt der Unterfenster Minimierungs-Modus
//    die Größe des Indikator Unterfensters
   if(m_height_subwindow_mode)
      if(m_rollup_subwindow_mode)
         ChangeSubwindowHeight(m_subwindow_height);
  }

Nun müssen wir noch eine weitere Methode erzeugen, welche den String-Parameter der Chart-Events akzeptiert. Die Überprüfung welches Objekt angeklickt wurde, wird mit dem String-Parameter, ähnlich wie es auch in der CWindow::CloseWindow() Method geschieht, durchgeführt. Je nachdem welcher Button angeklickt wurde, wird die entsprechende Methode in dem oben aufgeführten Programmcode aufgerufen. Lassen Sie uns diese Methode CWindow::ChangeWindowState() nennen. Lassen Sie uns diese Deklaration, Implementation und den Aufruf zu dem Chart-Eventhandler in der CWindow Klasse einfügen:

class CWindow: public CElement
  {
public:
   //--- Ändern des Fenster-Status
   bool              ChangeWindowState(const string pressed_object);
  };
//+----------------------------------------------------------------+
//| Chart Eventhandler                                             |
//+----------------------------------------------------------------+
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Behandeln des Objekt-Click Events
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Minimieren und maximieren des Fensters
      ChangeWindowState(sparam);
      return;
     }
  }
//+----------------------------------------------------------------+
//| Überprüfen des Fenster-minimierungs- und maximierungs- Events  |
//+----------------------------------------------------------------+
bool CWindow::ChangeWindowState(const string pressed_object)
  {
//--- Falls der Button "Minimierung des Fensters" gedrückt wurde
   if(pressed_object==m_button_rollup.Name())
     {
      RollUp();
      return(true);
     }
//--- Falls der Button "Maximierung des Fensters" gedrückt wurde
   if(pressed_object==m_button_unroll.Name())
     {
      Unroll();
      return(true);
     }
//---
   return(false);
  }

Kompilieren Sie alle Dateien, die sich geändert haben und die Programmdateien für den Test. Das erwartete Ergebnis unserer Arbeit ist die Möglichkeit unser Fenster nun minimieren und maximieren zu können. Der nachfolgende Screenshot zeigt unser Formular in einem minimierten Modus:

Abbildung 3. Testen der Funktionalitäten unseres Formulars.

Abbildung 3. Testen der Funktionalitäten unseres Formulars.

Es funktioniert. Wir können unser Formular mit den Steuerelementen über den Charts hinweg bewegen, jedes Objekt reagiert auf den Mauszeiger und die Buttons Arbeiten entsprechend der gewünschten Funktion.

 

Entfernen der Interface-Elemente

Wenn Sie den vorgeschlagenen Schritten dieses Artikels gefolgt sind, dann können Sie jetzt sehen, dass auch alle Objekte des grafischen Interfaces gelöscht werden, wenn ein EA vom Chart entfernt wird. Wir haben allerdings bisher noch nicht besprochen, wie die Methoden für das Löschen von grafischen Objekten aussieht. Wieso werden die Objekte gelöscht, wenn der EA vom Chart entfernt wird? Dieses geschieht in der Standardbibliothek der Klassen, um genauer zu sein, in dem Destruktor der CChartObject Klasse, den abgeleiteten Klassen, welche wir in unserer Bibliothek verwenden. Wenn ein Programm vom Chart entfernt wird, werden alle Destruktoren der Klassen, einschließlich dieser hier, aufgerufen. Wenn ein Objekt dem Chart hinzugefügt wurde, dann wird dieses auch gelöscht:

//+----------------------------------------------------------------+
//| Destruktor                                                     |
//+----------------------------------------------------------------+
CChartObject::~CChartObject(void)
  {
   if(m_chart_id!=-1)
      ::ObjectDelete(m_chart_id,m_name);
  }

Wenn sich nur das Chart-Symbol oder die Timeframe ändert, dann werden die Destruktoren nicht aufgerufen und die grafischen Objekte werden somit auch nicht gelöscht. Da das graphische Interface in der OnInit() Methode der Haupt-Programmdatei erzeugt wird, und eine die Deinitialisierung und Initialisierung bei dem Wechsels des Symbols oder der Timeframe durchgeführt wird, wird bei jedem Wechsel des Symbols oder der Timeframe ein neues Interface auf das bereits existierende gelegt. Als Ergebnis erhalten wir bei dem ersten Wechsel zwei Kopien der Objekte. Wenn Sie nun mit dem Wechsel der Symbole oder der Timeframe fortfahren, erhalten Sie mehrere Kopien der Interface-Objekte.

Abbildung 4. Testen des Formulars beim Wechsel zwischen Symbolen und Timeframes.

Abbildung 4. Testen des Formulars beim Wechsel zwischen Symbolen und Timeframes.

Wir müssen also in unserer Bibliothek sicherstellen, dass wenn ein Programm deinitialisiert wird, auch alle grafischen Objekte entfernt werden. Ebenso müssen auch alle Arrays mit Pointern zu diesen Objekten geleert werden und deren Größe auf Null gesetzt werden. Nachdem wir diese Änderungen vorgenommen haben, werden in die Moment der Initialisierung keine weiteren Klone unserer Objekte mehr erstellt. Lassen Sie uns diesen Mechanismus nun implementieren.

Die virtuelle Delete() Method wird in der CElement Klasse deklariert. Jede von der CElement abgeleiteten Klasse wird eine eigene Implementation dieser Methode haben. Die Delete() Methode haben wir schon zuvor in der CWindow Klasse deklariert. Nun müssen wir diese Methode nur noch implementieren. In dieser Methode werden einige Variablen zurückgesetzt, alle Control-Objekte werden gelöscht und auch das Array mit den Pointern zu den Objekten - innerhalb der Basis Kasse - wird geleert.

//+----------------------------------------------------------------+
//| Löschen                                                        |
//+----------------------------------------------------------------+
void CWindow::Delete(void)
  {
//--- Zurücksetzen der Variablen
   m_right_limit=0;
//--- Entfernen der Objekte
   m_bg.Delete();
   m_caption_bg.Delete();
   m_icon.Delete();
   m_label.Delete();
   m_button_close.Delete();
   m_button_rollup.Delete();
   m_button_unroll.Delete();
   m_button_tooltip.Delete();
//--- Löschen des Objekt-Arrays
   CElement::FreeObjectsArray();
//--- Zurücksetzen des Control-Focus
   CElement::MouseFocus(false);
  }

Der Zugriff auf die Delete() Methoden aller Interface-Controls kann über die CWndEvents Klasse erfolgen. Dafür erzeugen wir die CWndEvents::Destroy() Methode, die das grafische Interface löscht. In dieser Methode werden alle existierenden Controls dieser Form durchlaufen und es wird die Methode Delete() von jedem Control aufgerufen. Bevor die Delete() Methode aufgefufen wird, sollte der Pointer verifiziert werden. Nachdem die Objekte gelöscht wurden, muss das Control-Array noch geleert werden. Nachdem die Formular-Schleife verlassen wurde, müssen deren Arrays auch noch geleert werden.

Der nachfolgende Programmcode zeigt die Deklaration und Implementation der CWndEvents::Destroy() Methode:

class CWndEvents : public CWndContainer
  {
protected:
   //--- Entfernen des Interfaces
   void              Destroy(void);
  };
//+----------------------------------------------------------------+
//| Löschen aller Objekte                                          |
//+----------------------------------------------------------------+
void CWndEvents::Destroy(void)
  {
   int window_total=CWndContainer::WindowsTotal();
   for(int w=0; w<window_total; w++)
     {
      int elements_total=CWndContainer::ElementsTotal(w);
      for(int e=0; e<elements_total; e++)
        {
         //--- Wenn der Pointer nicht gültig ist, dann nimm den nächsten
         if(::CheckPointer(m_wnd[w].m_elements[e])==POINTER_INVALID)
            continue;
         //--- Entfernen der Control-Objekte
         m_wnd[w].m_elements[e].Delete();
        }
      //--- Leeren der Control-Arrays
      ::ArrayFree(m_wnd[w].m_objects);
      ::ArrayFree(m_wnd[w].m_elements);
     }
//--- Leeren der Formular-Arrays
   ::ArrayFree(m_wnd);
   ::ArrayFree(m_windows);
  }

Rufen Sie nun die Destroy() Methode innerhalb der CProgram::OnDeinitEvent() Methode in Verbindung mit der OnDeinit() Funktion der Haupt-Programmdatei auf. Fügen Sie folgendes hinzu:

//+----------------------------------------------------------------+
//| Deinitialisierung                                              |
//+----------------------------------------------------------------+
void CProgram::OnDeinitEvent(const int reason)
  {
   //--- Entfernen des Interfaces
   CWndEvents::Destroy();  
  }

Kompilieren Sie alle Dateien, in welchen Veränderung stattgefunden haben und die Haupt-Programmdatei. Laden Sie den EA auf den Chart und wechseln Sie das Symbol und die Timeframe ein paar Mal. Alles sollte nun einwandfrei funktionieren. Bis heute noch keine weiteren Klone mehr auftreten. Problem gelöst.

 

Schlussfolgerung

In dem nächsten Kapitel werden wir weitere Tests durchführen, um zu sehen, wie unser Formular in anderen Programm-Typen, wie zum Beispiel Indikatoren und Skripten, arbeitet. Wir werden zu dem Tests in dem MetaTrader 4 Terminal durchführen, da es auch unser ursprüngliches Ziel war eine plattformübergreifende Bibliothek für grafische Interfaces zu erzeugen.

Sie finden hier das gesamte Material des ersten Kapitels und können dieses herunterladen und ausprobieren. 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 Artikel (Kapitel) des ersten Teils: