Grafische Interfaces IV: Der Multi-Window-Modus und das System für Prioritäten (Kapitel 2)

Anatoli Kazharski | 5 Juli, 2016

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.  

In dem vorherigen Kapitel haben wir die Statusbar und das Tooltip-Element des grafischen Interfaces besprochen. In diesem Kapitel werden wir die Bibliothek um die Möglichkeit der Erzeugung von Multi-Window-Modus-Interfaces für MQL Anwendungen erweitern. Zudem entwickeln wir ein System für die Prioritäten eines Klicks mit der linken Maustaste auf grafische Objekte, da ohne dieses System ein Klick auf ein Control eventuell unbeantwortet bleibt.


Der Multi-Window-Modus

Lassen Sie uns den Multi-Window-Modus des grafischen Interfaces unserer zu entwickelnden Bibliothek betrachten. Bis jetzt bietet die ENUM_WINDOW_TYPE Enumeration zwei Bezeichner: für das Haupt-(W_MAIN) und das Dialog (W_DIALOG) -Fenster (Window). Der Ein-Fenster-Modus war der einzige Modus, den wir gebraucht haben. Nachdem wir ein paar Ergänzungen hinzugefügt haben, muss für das Aktivieren des Multi-Window-Modus lediglich noch die Erzeugung und das Hinzufügen der benötigten Anzahl von Formularen zur Basis erfolgen.

Erzeugen sie in der Hauptklasse für die Event-Bearbeitung CWndEvents eine Variable für das Abspeichern des Index des aktuellen aktiven Fensters.

class CWndEvents : public CWndContainer
  {
protected:
   //--- Index des aktiven Fensters
   int               m_active_window_index;
  };

Lassen Sie uns nun ansehen, wie der Index des aktiven Fensters identifiziert werden kann. Wenn nun zum Beispiel der Anwender einem Button ein Dialogfenster (W_DIALOG) zuweist: Sind nun der Button gedrückt wird, dann wird das ON_CLICK_BUTTON Event erzeugt. Dieses Event kann in dem CProgram::OnEvent() Eventhandler der Benutzerklasse nachverfolgt werden. Wir verwenden zudem die CWindow::Show() Methode des Formulars welche noch gezeigt werden muss. Die aktuelle Implementation der Bibliothek ist noch nicht ausreichend und wir werden die notwendigen Erweiterungen vorstellen.

Von der CWindow::Show() Methode aus muss ein benutzerdefiniertes Event gesendet werden, Welches einen Hinweis darauf gibt, dass ein Fenster geöffnet wurde und somit manche Parameter des graphischen Interfaces aktualisiert werden müssen. Ein solches Event benötigt einen separaten Bezeichner. Lassen Sie uns diesen ON_OPEN_DIALOG_BOX nennen und wir platzieren diesen in der Defines.mqh Datei, wo wir auch schon andere Bezeichnung für diese Bibliothek abgelegt haben. 

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#define ON_OPEN_DIALOG_BOX        (11) // Das Event für das Öffnen eines Dialogfensters

Fügen Sie am Ende der CWindow::Show() Methode die nachfolgende Programmzeile hinzu. Sie sehen hier eine abgekürzte Version dieser Methode. Für die eindeutige Identifizierung des Initiators dieses Events, muss zusätzlich der Element-Bezeichner und der Programmname gesendet werden.

//+------------------------------------------------------------------+
//| Zeigt das Fenster                                                |
//+------------------------------------------------------------------+
void CWindow::Show(void)
  {
//--- Alle Objekte sichtbar machen
//--- Status der Sichtbarkeit
//--- Zurücksetzen des Fokus
//--- Eine Nachricht darüber senden
   ::EventChartCustom(m_chart_id,ON_OPEN_DIALOG_BOX,(long)CElement::Id(),0,m_program_name);
  }

Dieses Event wird in der CWndEvents Klasse behandelt. Bevor wir mit der Implementation dieser Methode fortfahren, müssen wir noch drei weitere Methoden in der CWindow Klasse erzeugen. Hierbei handelt es sich um zwei Methoden für das Abspeichern und der Abfrage des Index des Formulars, von welchem aus das Dialogfenster geöffnet wurde, sowie eine Methode für die Verwaltung des Status dieses Formulars. 

Der Index des zuvor geöffneten Fensters muss abgespeichert werden, da es gleichzeitig mehrere geöffnete Fenster geben kann. Dieses brauchen wir, damit wir wissen, nachdem ein Dialogfenster geschlossen wurde, welches Fenster wieder aktiviert werden muss. 

class CWindow : public CElement
  {
private:
   //--- Index des vorherigen aktiven Fensters
   int               m_prev_active_window_index;
   //---
public:
   //--- (1) Abspeichern unf (2) Abfragen des Index des vorherigen aktiven Fensters
   void              PrevActiveWindowIndex(const int index)                  { m_prev_active_window_index=index;   }
   int               PrevActiveWindowIndex(void)                       const { return(m_prev_active_window_index); }
  };

Bei der Verwaltung des Status eines Formulars, geben wir deaktivierten Formularen eine unterschiedliche Farbe der Kopfzeile, welche von dem Anwender geändert werden kann. Die Farbe der Elemente wird sich nicht ändern, wenn sich der Mauszeiger über ihnen befindet, solange das Formular gesperrt ist. Dementsprechend muss bei der Deaktivierung eines Formulars ein benutzerdefiniertes Event erzeugt werden. Dieses gibt an, dass das Formular gesperrt ist und der Fokus und die Farben seiner Elemente zurückgesetzt werden müssen. Wenn ein Formular gesperrt ist, dann wird der Fokus seiner Elemente nicht nachverfolgt. In dem Moment des Öffnens eines Dialogfensters, ist die Farbe des Elementes, welches dieses Dialogfenster geöffnet hat, gleich der Farbe wie wenn sich der Mauszeiger über dem Element befindet. 

Dafür wird der ON_RESET_WINDOW_COLORS Bezeichner in der Defines.mqh Datei hinterlegt:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#define ON_RESET_WINDOW_COLORS    (13) // Zurücksetzen der Farben alle Elemente des Formulars

In den nachfolgenden Code werden die Methoden für die Verwaltung des Status des Formulars gezeigt:

class CWindow : public CElement
  {
public:
   //--- Festlegen des Status des Fensters
   void              State(const bool flag);
  };
//+-----------------------------------------------------------------+
//| Festlegen des Status des Fensters                               |
//+-----------------------------------------------------------------+
void CWindow::State(const bool flag)
  {
//--- Falls das Fenster gesperrt werden muss
   if(!flag)
     {
      //--- Den Status setzen
      m_is_locked=true;
      //--- Die Farbe der Kopfzeile festlegen
      m_caption_bg.BackColor(m_caption_bg_color_off);
      //--- Signal für das Zurücksetzen der Farben. Dieser Reset wird auch bei anderen Elementen ausgeführt.
      ::EventChartCustom(m_chart_id,ON_RESET_WINDOW_COLORS,(long)CElement::Id(),0,"");
     }
//--- Falls das Fenster entsperrt werden muss
   else
     {
      //--- Den Status setzen
      m_is_locked=false;
      //--- Die Farbe der Kopfzeile festlegen
      m_caption_bg.BackColor(m_caption_bg_color);
      //--- Den Fokus löschen
      CElement::MouseFocus(false);
     }
  }

Lassen Sie uns auf die Verarbeitung des ON_OPEN_DIALOG_BOX Events zurückkommen. In der Hauptklasse für das Verarbeiten der Events des grafischen Interfaces(CWndEvents) erzeugen Sie die CWndEvents::OnOpenDialogBox() Methode, die in der CWndEvents::ChartEventCustom() Methode für die Verarbeitung aller benutzerdefinierten Events aufgerufen wird.

Die CWndEvents::OnOpenDialogBox() Methode beginnt mit zwei Überprüfungen: Eine für den Bezeichner des Events und eine für den Programmnamen. Wenn diese erfolgreich abgeschlossen worden sind, dann müssen wir alle Fenster durchgehen, um herauszufinden, welches Fenster dieses Event erzeugt hat. Der Element Bezeichner, der in dieser Nachricht als Parameter (lparam) enthalten ist, erleichtert das. Alle Formulare, die keinen entsprechenden Bezeichner besitzen, werden zusammen mit allen hinzugefügten Elementen gesperrt. Die Prioritäten alle Objekte werden mit Hilfe der ResetZorders() Methode zurückgesetzt und reagieren nicht mehr auf einen Klick mit der linken Maustaste. Wenn wir zu dem Formular gelangt sind, welches den passenden Bezeichner besitzt, speichern wir den Index des zur Zeit aktiven Fensters als den Index des vorherigen aktiven Fensters ab. Wir Aktivieren dieses Formular und stellen die Prioritäten für einen Klick mit der linken Maustaste für das Formular und allen seinen enthaltenen Elementen wieder her. Dann speichern wir den Index dieses Fensters als das zurzeit aktive Fenster ab. Anschließend machen wir alle Elemente dieses Formulars wieder sichtbar und stellen die Prioritäten der linken Maustaste wieder her, bis auf das Formularelement, da es bereits sichtbar ist und die Dropdown-Elemente. 

Wenn sich ein Dialogfenster öffnet während ein Tooltip sichtbar ist, dann muss der Tooltip versteckt werden. Es verschwindet nicht von selbst, da das Formular, zu welchem es hinzugefügt wurde, bereits gesperrt ist. Wir haben zuvor ein privates Array für Tooltips erzeugt, was in diesen Fällen Anwendung findet. Den Zugriff auf alle Methoden von jedem Element in der Basis kann man über die CWndEvents Hauptklasse für das Behandeln von Events erhalten. 

class CWndEvents : public CWndContainer
  {
private:
   //--- Öffnen eines Dialogfensters
   bool              OnOpenDialogBox(void);
  };
//+------------------------------------------------------------------+
//| CHARTEVENT_CUSTOM event                                          |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- Falls es sich um ein Signal zur Minimierung des Formulars handelt
//--- Falls es sich um ein Signal für die Maximierung des Formulars handelt
//--- Falls es sich um ein Signal zum Verstecken des Kontextmenüs unterhalb des auslösenden Menüelementes handelt
//--- Falls es sich um ein Signal für das Verstecken aller Kontextmenüs handelt

//--- Falls es sich um ein Signal zum Öffnen eines Dialogfensters handelt
   if(OnOpenDialogBox())
      return;
  }
//+------------------------------------------------------------------+
//| ON_OPEN_DIALOG_BOX event                                         |
//+------------------------------------------------------------------+
bool CWndEvents::OnOpenDialogBox(void)
  {
//--- Falls es sich um ein Signal zum Öffnen eines Dialogfensters handelt
   if(m_id!=CHARTEVENT_CUSTOM+ON_OPEN_DIALOG_BOX)
      return(false);
//--- Abbrechen, falls es sich um eine Nachricht eines anderen Programms handelt
   if(m_sparam!=m_program_name)
      return(true);
//--- Durchlaufendes Fenster-Arrays
   int window_total=CWndContainer::WindowsTotal();
   for(int w=0; w<window_total; w++)
     {
      //--- Wenn die Bezeichner übereinstimmen
      if(m_windows[w].Id()==m_lparam)
        {
         //--- Abspeichern des Index des Fensters in dem Formular, aus welchen dieses Formular gestartet wurde
         m_windows[w].PrevActiveWindowIndex(m_active_window_index);
         //--- Aktivierung des Formulars
         m_windows[w].State(true);
         //--- Wiederherstellen der Prioritäten eines Klicks mit der linken Maustaste bei den Objekten des Formulars
         m_windows[w].SetZorders();
         //--- Abspeichern des Index des aktivierten Fensters
         m_active_window_index=w;
         //--- Alle Elemente des aktivierten Fensters sichtbar machen
         int elements_total=CWndContainer::ElementsTotal(w);
         for(int e=0; e<elements_total; e++)
           {
            //--- Überspringen des Formulars und der Dropdown-Elemente
            if(m_wnd[w].m_elements[e].ClassName()=="CWindow" || 
               m_wnd[w].m_elements[e].IsDropdown())
               continue;
            //--- Das Element sichtbar machen
            m_wnd[w].m_elements[e].Show();
            //--- Wiederherstellen der Priorität der linken Maustaste bei diesem Element
            m_wnd[w].m_elements[e].SetZorders();
           }
         //--- Verstecken von Tooltips
         int tooltips_total=CWndContainer::TooltipsTotal(m_windows[w].PrevActiveWindowIndex());
         for(int t=0; t<tooltips_total; t++)
            m_wnd[m_windows[w].PrevActiveWindowIndex()].m_tooltips[t].FadeOutTooltip();
        }
      //--- Alle anderen Formulare werden gesperrt, bis das aktuelle Fenster geschlossen wird
      else
        {
         //--- Blockieren der Form
         m_windows[w].State(false);
         //--- Zurücksetzen der Prioritäten für die linke Maustaste für die Formularelemente
         int elements_total=CWndContainer::ElementsTotal(w);
         for(int e=0; e<elements_total; e++)
            m_wnd[w].m_elements[e].ResetZorders();
        }
     }
//---
   return(true);
  }

Nun werden wir den ON_RESET_WINDOW_COLORS Bezeichne ansprechen, welchen wir zuvor schon in diesem Artikel erwähnt haben. Bevor wir eine Methode für die Behandlung dieses Events schreiben, müssen wir noch eine Standard virtuelle Methode zu der CElement Basisklasse aller Elemente hinzufügen,die dafür gedacht ist, die Farben zurück zu setzen. Lassen Sie sie uns CElement::ResetColors() nennen:

class CElement#
  {
public:
   //--- Zurücksetzen der Element-Farben
   virtual void      ResetColors(void) {}
  };

Die ResetColors() Methode muss in allen abgeleiteten Klassen mit den spezifischen Charakteristiken für jedes Element erzeugt werden. Das nachfolgende Codebeispiel zeigt diese Methode für das Element des Icon-Buttons(CIconButton). Für alle anderen Elemente finden Sie die ResetColors() Methode in den an diesen Artikel angehängten Dateien.

class CIconButton : public CElement
  {
public:
   //--- Zurücksetzen der Element-Farben
   void              ResetColors(void);
  };
//+------------------------------------------------------------------+
//| Zurücksetzen der Farben                                          |
//+------------------------------------------------------------------+
void CIconButton::ResetColors(void)
  {
//--- Abbrechen, falls dieses der 2-Status-Modus ist und der Button gedrückt ist
   if(m_two_state && m_button_state)
      return;
//--- Zurücksetzen der Farbe
   m_button.BackColor(m_back_color);
//--- Den Fokus löschen
   m_button.MouseFocus(false);
   CElement::MouseFocus(false);
  }

Die Anwesenheit der virtuelle Methode in der Basisklasse der Elemente und der speziellen Versionen in den abgeleiteten Klassen, bieten die Möglichkeit, die Farben aller Elemente in einer Schleife eines Eventhandlers zurückzusetzen.

Wir schreiben die CWndEvents::OnResetWindowColors() Methode für die Behandlung des ON_RESET_WINDOW_COLORS Events. Diese sind recht simpel. Wir suchen über den Element-Bezeichner einer Nachricht (Events) nach dem Formular, welches gerade deaktiviert wurde. Falls es ein Formular gibt, dann speichern wir den Index. Wenn der Index gespeichert worden ist, dann setzen wir die Farben aller Elemente dieses Formulars zurück. Die Details diese Methode finden Sie in dem nachfolgenden Programmcode. 

class CWndEvents : public CWndContainer
  {
private:
   //--- Zurücksetzen der Farbe des Formulars und seiner Elemente
   bool              OnResetWindowColors(void);
  };
//+------------------------------------------------------------------+
//| CHARTEVENT_CUSTOM event                                          |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- Falls es sich um ein Signal zur Minimierung des Formulars handelt
//--- Falls es sich um ein Signal für die Maximierung des Formulars handelt
//--- Falls es sich um ein Signal zum Verstecken des Kontextmenüs unterhalb des auslösenden Menüelementes handelt
//--- Falls es sich um ein Signal für das Verstecken aller Kontextmenüs handelt
//--- Falls es sich um ein Signal zum Öffnen eines Dialogfensters handelt
//--- Falls es sich um ein Signal handelt, die Farben alle Elemente eines angegebene Formulars zurückzusetzen
   if(OnResetWindowColors())
      return;
  }
//+------------------------------------------------------------------+
//| ON_RESET_WINDOW_COLORS event                                     |
//+------------------------------------------------------------------+
bool CWndEvents::OnResetWindowColors(void)
  {
//--- Falls es sich um ein Signal für das Zurücksetzen der Fensterfarbe handelt
   if(m_id!=CHARTEVENT_CUSTOM+ON_RESET_WINDOW_COLORS)
      return(false);
//--- Für die Identifikation des Index des Formulars, von welchem aus diese Nachricht empfangen wurde
   int index=WRONG_VALUE;
//--- Durchlaufendes Fenster-Arrays
   int window_total=CWndContainer::WindowsTotal();
   for(int w=0; w<window_total; w++)
     {
      //--- Wenn die Bezeichner übereinstimmen
      if(m_windows[w].Id()==m_lparam)
        {
         //--- Abspeichern des Index
         index=w;
         //--- Zurücksetzen der Farben des Formulars
         m_windows[w].ResetColors();
         break;
        }
     }
//--- Abbrechen, falls der Index nicht identifiziert werden konnte
   if(index==WRONG_VALUE)
      return(true);
//--- Zurücksetzen der Farben aller Elemente des Formulars
   int elements_total=CWndContainer::ElementsTotal(index);
   for(int e=0; e<elements_total; e++)
      m_wnd[index].m_elements[e].ResetColors();
//--- Neuzeichnen auf dem Chart
   m_chart.Redraw();
   return(true);
  }

Wir haben jetzt das Öffnen von Fenstern geklärt. Jetzt benötigen wir die Methoden für das Schließen des Fensters und Wiederherstellen des vorherigen aktiven Fensters. Um ein solches Event bearbeiten zu können, müssen wir den ON_CLOSE_DIALOG_BOX Bezeichner in der Defines.mqh Datei definieren.

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#define ON_CLOSE_DIALOG_BOX       (12) // Event für das Schließen eines Dialogfensters

In der CWindow Klasse verwenden wir für das Schließen des Formulars und des Programms die CWindow::CloseWindow() Methode. In dieser Methode ist der Abschnitt für das Schließen eines Dialogs-Fensters(W_DIALOG) noch nicht implementiert. Lassen Sie uns eine zusätzliche Methode schreiben, die ein Event für das Schließen von Dialogfenstern generiert. Zu dem (1) Bezeichner des Events, beinhaltet die Nachricht noch den (2) Bezeichner des Elementes, (3) den Index des zuvor geöffneten und aktiven Fensters und (4) den Text der Kopfzeile. Lassen Sie uns diese Methode CWindow::CloseDialogBox() nennen. Wir werden diese auch später in komplexen Controls verwenden, wo das Schließen eines Fensters auch von anderen Elementen als einem Closebutton durchgeführt wird.

class CWindow : public CElement
  {
public:
   //--- Schließen eines Dialogfensters
   void              CloseDialogBox(void);
  };
//+------------------------------------------------------------------+
//| Schließen eines Dialogfensters                                   |
//+------------------------------------------------------------------+
void CWindow::CloseDialogBox(void)
  {
//--- Status der Sichtbarkeit
   CElement::IsVisible(false);
//--- Eine Nachricht darüber senden
   ::EventChartCustom(m_chart_id,ON_CLOSE_DIALOG_BOX,CElement::Id(),m_prev_active_window_index,m_caption_text);
  }

In der CWindow Klasse muss die CWindow::CloseDialogBox() Methode in der CWindow::CloseWindow() Methode aufgerufen werden, so wie es in dem nachfolgenden abgekürzten Programmcode gezeigt ist. Sie finden die vollständigen Versionen in den Dateien die diesem Artikel beigefügt sind.

//+------------------------------------------------------------------+
//| Schließen eines Dialogsfensters oder des Programms               |
//+------------------------------------------------------------------+
bool CWindow::CloseWindow(const string pressed_object)
  {
//--- Falls nicht auf den Close-Button geklickt 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 Dialogfenster handelt
   else if(m_window_type==W_DIALOG)
     {
      //--- Schließe es
      CloseDialogBox();
     }
//---
   return(false);
  }

Nachdem die Nachricht mit dem ON_CLOSE_DIALOG_BOX Bezeichner gesendet wurde, muss dieses in dem Eventhandler der CWndEvents Klasse nachverfolgt und behandelt werden. Lassen Sie uns dafür die CWndEvents::OnCloseDialogBox() Methode schreiben. Wir durchlaufen alle Fenster in der Basis und halten nach dem Fenster Ausschau, zu welchem der Bezeichner in der Nachricht passt. Wenn ein solches Fenster gefunden wird, muss es deaktiviert werden. Anschließen wird es zusammen mit seinen Elementen versteckt und das Formular, dessen Index in der Nachricht übergeben wurde, wird aktiviert. Anschließend wird der Index des aktuell aktiven Fensters abgespeichert und die Prioritäten für einen Klick mit der linken Maustaste wird für alle Elemente wiederhergestellt.

class CWndEvents : public CWndContainer
  {
private:
   //--- Schließen eines Dialogfensters
   bool              OnCloseDialogBox(void);
  };
//+------------------------------------------------------------------+
//| CHARTEVENT_CUSTOM event                                          |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- Falls es sich um ein Signal zur Minimierung des Formulars handelt
//--- Falls es sich um ein Signal für die Maximierung des Formulars handelt
//--- Falls es sich um ein Signal zum Verstecken des Kontextmenüs unterhalb des auslösenden Menüelementes handelt
//--- Falls es sich um ein Signal für das Verstecken aller Kontextmenüs handelt
//--- Falls es sich um ein Signal zum Öffnen eines Dialogfensters handelt
//--- Falls es sich um ein Signal zum Schließen eines Dialogs Fensters handelt
   if(OnCloseDialogBox())
      return;
//--- Falls es sich um ein Signal handelt, die Farben alle Elemente eines angegebene Formulars zurückzusetzen
  }
//+------------------------------------------------------------------+
//| ON_CLOSE_DIALOG_BOX event                                        |
//+------------------------------------------------------------------+
bool CWndEvents::OnCloseDialogBox(void)
  {
//--- Falls es sich um ein Signal zum Schließen eines Dialogs Fensters handelt
   if(m_id!=CHARTEVENT_CUSTOM+ON_CLOSE_DIALOG_BOX)
      return(false);
//--- Durchlaufendes Fenster-Arrays
   int window_total=CWndContainer::WindowsTotal();
   for(int w=0; w<window_total; w++)
     {
      //--- Wenn die Bezeichner übereinstimmen
      if(m_windows[w].Id()==m_lparam)
        {
         //--- Blockieren der Form
         m_windows[w].State(false);
         //--- Verstecken des Formulars
         int elements_total=CWndContainer::ElementsTotal(w);
         for(int e=0; e<elements_total; e++)
            m_wnd[w].m_elements[e].Hide();
         //--- Aktivieren des vorherigen Formulars
         m_windows[int(m_dparam)].State(true);
         //--- Neuzeichnen auf dem Chart
         m_chart.Redraw();
         break;
        }
     }
//--- Setzen des Index des vorherigen Fensters
   m_active_window_index=int(m_dparam);
//--- Wiederherstellen der Prioritäten für eines Klicks mit der linken Maustaste bei dem aktivierten Fenster
   int elements_total=CWndContainer::ElementsTotal(m_active_window_index);
   for(int e=0; e<elements_total; e++)
      m_wnd[m_active_window_index].m_elements[e].SetZorders();
//---
   return(true);
  }

Nun ist alles bereit für den Test des Multi-Window-Modus.  

 


Test des Multi-Window-Modus

Erzeugen sie In dem EA, den wir auch schon für das Testen der informierenden Interface-Elemente verwendet haben, zwei Instanzen der CWindow Klasse. Als Ergebnis erhalten wir drei Formulare in den grafischen Interface dieses EAs. Das erste Formular ist das Hauptformular (W_MAIN) Und die beiden anderen Formulare übernehmen die Rolle von Dialogfenstern (W_DIALOG). Fügen Sie ein Dialogfenster einem der Buttons des Hauptformulars hinzu. Erzeugen Sie drei Buttons in dem ersten Dialogfenster und fügen Sie das zweite Dialogfenster einem dieser neu erzeugten Buttons hinzu. Auf diese Weise können wir drei geöffnete Fenster erhalten, wobei nur eines von diesen das aktive (verfügbare) Fenster sein wird.

Der nachfolgende Programmcode zeigt, was der CProgram benutzerdefinierten Klasse der Anwendung zu dem aktuellen Stand der Entwicklung noch hinzugefügt werden muss. 

class CProgram : public CWndEvents
  {
private:
   //--- Form 2
   CWindow           m_window2;
   //--- Icon Buttons
   CIconButton       m_icon_button6;
   CIconButton       m_icon_button7;
   CIconButton       m_icon_button8;

   //--- Form 3
   CWindow           m_window3;
   //---
private:
   //--- Form 2
   bool              CreateWindow2(const string text);
   //--- Icon Buttons
#define ICONBUTTON6_GAP_X        (7)
#define ICONBUTTON6_GAP_Y        (25)
   bool              CreateIconButton6(const string text);
#define ICONBUTTON7_GAP_X        (7)
#define ICONBUTTON7_GAP_Y        (50)
   bool              CreateIconButton7(const string text);
#define ICONBUTTON8_GAP_X        (7)
#define ICONBUTTON8_GAP_Y        (75)
   bool              CreateIconButton8(const string text);

   //--- Form 3
   bool              CreateWindow3(const string text);
  };

Bringen Sie den Aufruf dieser Methoden in der Hauptmethode für das Erzeugen des grafischen Interfaces unter. Nachfolgend ist eine verkürzte Version dieser Methode dargestellt. 

//+------------------------------------------------------------------+
//| Erzeugung des Trading-Panels                                     |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Erzeugen des Formulars 1 für die Controls
//---Erzeugung der Controls:
//    Hauptmenü
//--- Kontextmenüs
//--- Erzeugen der Statusbar
//--- Icon Buttons

//--- Erzeugung des Formulars 2 für Controls
   if(!CreateWindow2("Icon Button 1"))
      return(false);
//--- Icon Buttons
   if(!CreateIconButton6("Icon Button 6..."))
      return(false);
   if(!CreateIconButton7("Icon Button 7"))
      return(false);
   if(!CreateIconButton8("Icon Button 8"))
      return(false);

//--- Erzeugung des Formulars 3 für Controls
   if(!CreateWindow3("Icon Button 6"))
      return(false);

//--- Tooltips
//--- Neuzeichnen auf dem Chart
   m_chart.Redraw();
   return(true);
  }

Wir betrachten hier nur die Methoden für das erste Dialogfenster (Das zweite Formular). Wie sie sich wahrscheinlich erinnern, müssen Sie die CWndContainer::AddWindow() Methode für das Hinzufügen eines Formulars zu der Basis verwenden. Bitte beachten Sie wie die Formular Koordinaten definiert werden. Dieses wird in dem nachfolgenden Programmcode gezeigt. Da die Standard-Koordinaten 0 sind, wenn das Programm auf den Chart geladen wird, sollten Sie neue geeignete Koordinaten festlegen. In diesem Beispiel sind die Werte: x=1, y=20. Anschließend kann das Formular bewegt werden und die Timeframe oder das Symbol des Charts kann geändert werden. Der nachfolgende Programmcode zeigt, dass das Formular an der Stelle bleiben wo ist auch zuletzt gewesen ist. Wenn Sie es vorziehen, dass das Formular an dem Punkt erscheint, wo ist zum ersten Mal auf den Chart geladen worden ist, dann entfernen Sie diese Bedingungen. In diesem Beispiel, besitzen alle drei Formulare des grafischen Interfaces dieses Programms die selben Bedingungen.

Lassen Sie uns festlegen, dass das Dialogfenster über den Chart bewegt werden kann. Der Typ des Fensters sollte als in Dialog (W_DIALOG) Festgelegt werden, andernfalls werden sie eine fehlerhafte Funktionsweise des grafischen Interfaces feststellen. Das Icon des Fensters kann über die CWindow::IconFile() Methode neu definiert werden. Im Falle von Dialogfenstern kann das selbe Icon verwendet werden, wie das Icon des Fensters, welches das Dialogfenster geöffnet hat.

//+------------------------------------------------------------------+
//| Erzeugt das Formular 2 für Controls                              |
//+------------------------------------------------------------------+
bool CProgram::CreateWindow2(const string caption_text)
  {
//--- Hinzufügen des Pointers des Fensters zu dem Fenster-Array
   CWndContainer::AddWindow(m_window2);
//--- Koordinaten
   int x=(m_window2.X()>0) ? m_window2.X() : 1;
   int y=(m_window2.Y()>0) ? m_window2.Y() : 20;
//--- Eigenschaften
   m_window2.Movable(true);
   m_window2.WindowType(W_DIALOG);
   m_window2.XSize(160);
   m_window2.YSize(160);
   m_window2.IconFile("Images\\EasyAndFastGUI\\Icons\\bmp16\\coins.bmp");
   m_window2.CaptionBgColor(clrCornflowerBlue);
   m_window2.CaptionBgColorHover(C'150,190,240');
//--- Erzeugen des Formulars
   if(!m_window2.CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
//---
   return(true);
  }

Lassen Sie sie mich an einige Details erinnern, wie Controls zu bestimmten Dialogfenstern hinzugefügt werden. Lassen Sie uns als Beispiel eine der Button-Methoden näher betrachten. Ich möchte hier nur zwei Dinge hervorheben. 

Sie müssen sich daran erinnern, dass:

//+------------------------------------------------------------------+
//| Erzeugt den Icon-Button 6                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateIconButton6(const string button_text)
  {
//--- Abspeichern des Fenster-Pointers
   m_icon_button6.WindowPointer(m_window2);
//--- Koordinaten
   int x=m_window2.X()+ICONBUTTON6_GAP_X;
   int y=m_window2.Y()+ICONBUTTON6_GAP_Y;
//--- Festlegen der Eigenschaften vor der Erzeugung
   m_icon_button6.TwoState(false);
   m_icon_button6.ButtonXSize(146);
   m_icon_button6.ButtonYSize(22);
   m_icon_button6.LabelColor(clrBlack);
   m_icon_button6.LabelColorPressed(clrBlack);
   m_icon_button6.BorderColorOff(clrWhite);
   m_icon_button6.BackColor(clrLightGray);
   m_icon_button6.BackColorHover(C'193,218,255');
   m_icon_button6.BackColorPressed(C'153,178,215');
   m_icon_button6.IconFileOn("Images\\EasyAndFastGUI\\Icons\\bmp16\\script.bmp");
   m_icon_button6.IconFileOff("Images\\EasyAndFastGUI\\Icons\\bmp16\\script_colorless.bmp");
//--- Erzeugung des Controls
   if(!m_icon_button6.CreateIconButton(m_chart_id,m_subwin,button_text,x,y))
      return(false);
//--- Hinzufügen des Pointers des Elementes zu der Basis
   CWndContainer::AddToElementsArray(1,m_icon_button6);
   return(true);
  }

Die Verwaltung der Anzeige der Fenster obliegt dem Entwickler. Verfolgen Sie das Anklicken von jedem Control in dem Eventhandler der CProgram Benutzerklasse und zeigen Sie das entsprechende Fenster. Verknüpfen Sie den Aufruf des ersten Dialogfensters Mit dem Button des Hauptfensters des EAs (Zweites Formular), und den Aufruf des zweiten Dialogfensters  Mit dem Button auf dem ersten Dialogfenster (Drittes Formular).

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Der Button-click-event
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- Wenn der Text uebereinstimmt
      if(sparam==m_icon_button1.Text())
        {
         //--- Zeige das zweite Fenster
         m_window2.Show();
        }
      //--- Wenn der Text uebereinstimmt
      if(sparam==m_icon_button6.Text())
        {
         //--- Zeige das dritte Fenster
         m_window3.Show();
        }
     }
  }

Der nachfolgende Screenshot zeigt das erwartete Ergebnis. Bitte beachten Sie die Punkte in den Button-Namen «Icon Button 1...» und «Icon Button 6...». Dieses ist eine gängige Methode, um dem Anwender darauf hinzuweisen, dass dieses Element ein Weiteres öffnet.

Abbildung  1. Test der Multi-Window-Modus

Abbildung 1. Test der Multi-Window-Modus

 

Wenn Sie nun das Symbol wechseln oder die Timeframe verändern, währen mehere Formulare/Fenster geöffnet sind, dann werden sie ein Problem feststellen. Die Dialogfenster verschwinden erwartungsgemäß, aber die Verwaltung wird nicht wieder an das Hauptfenster zurückgegeben. Somit reagiert das Fenster auch nicht mehr auf Aktionen des Anwenders. Die Lösung hierfür ist einfach. Wie sie sich wahrscheinlich erinnern, wird die CWndEvents::Destroy() Methode in der CProgram::OnDeinitEvent() Methode für die Deinitialisierung der benutzerdefinierten Klasse aufgerufen. In diese Methode wird das graphische Interface dieser Anwendung entfernt. In diesem Moment, wo das grafische Interface entfernt wird, muss die Verwaltung wieder an das Hauptfenster zurückgegeben werden. Dafür müssen einige Ergänzungen in der CWndEvents::Destroy() Methode vorgenommen werden:

Nachfolgend sehen Sie den Programmcode der aktuellen Version der CWndEvents::Destroy() Methode. 

//+------------------------------------------------------------------+
//| Löschen aller Objekte                                            |
//+------------------------------------------------------------------+
void CWndEvents::Destroy(void)
  {
//--- Festlegen des Index des Hauptfensters
   m_active_window_index=0;
//--- Die Anzahl der Fenster abfragen
   int window_total=CWndContainer::WindowsTotal();
//--- Durchlaufendes Fenster-Arrays
   for(int w=0; w<window_total; w++)
     {
      //--- Aktivieren des Hauptfensters
      if(m_windows[w].WindowType()==W_MAIN)
         m_windows[w].State(true);
      //--- Sperren der Dialogfenster
      else
         m_windows[w].State(false);
     }
//--- Leeren der Element-Arrays
   for(int w=0; w<window_total; w++)
     {
      int elements_total=CWndContainer::ElementsTotal(w);
      for(int e=0; e<elements_total; e++)
        {
         //--- Falls der Pointer ungültig ist, dann fahre mit dem nächsten fort
         if(::CheckPointer(m_wnd[w].m_elements[e])==POINTER_INVALID)
            continue;
         //--- Lösche die Element-Objekte
         m_wnd[w].m_elements[e].Delete();
        }
      //--- Leeren der Element-Arrays
      ::ArrayFree(m_wnd[w].m_objects);
      ::ArrayFree(m_wnd[w].m_elements);
      ::ArrayFree(m_wnd[w].m_context_menus);
     }
//--- Leeren der Formular-Arrays
   ::ArrayFree(m_wnd);
   ::ArrayFree(m_windows);
  }

Die erste Version des Multi-Window-Modus ist implementiert. Es hat sich herausgestellt, das es weniger kompliziert ist als es zunächst aussah.  

 


Erweiterung des Systems um die Verwaltung der Prioritäten von Klicks mit der linken Maustaste.

Bis jetzt wurde die Verwaltung der Prioritäten eines Klick mit der linken Maustaste auf ein Interface-Element durch die Events mit den Bezeichnern ON_OPEN_DIALOG_BOX und ON_CLOSE_DIALOG_BOX durchgeführt. Der Grund dafür war, dass, wenn das nächste Dropdown-Element entwickelt wurde, es dem Benutzer überlassen wurde, den Prioritätswert für jedes Objekt dieses Elementes zuzuweisen. Die Prioritäten von anderen Elementen, welche darunter lagen, wurden dabei berücksichtigt. Wenn es allerdings um die Erstellung von komplexen Verbindungen zwischen Controls geht, ist dieses System umständlich und führt leicht zu Verwirrungen. Um dieses einfacher gestalten zu können, lassen Sie uns zwei weitere Bezeichner für diese Events erzeugen:

Fügen Sie diese der Defines.mqh Datei hinzu.

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#define ON_ZERO_PRIORITIES        (14) // Zurücksetzen der Prioritäten der linken Maustaste
#define ON_SET_PRIORITIES         (15) // Wiederherstellen der Prioritäten der linken Maustaste

Die Erzeugung von Events mit diesen Bezeichnern, muss in den Klassen von Elementen erzeugt werden, welche Dropdown-Elemente sind oder sein könnten. Zum aktuellen Stand der Entwicklung, stellen Kontextmenü-Elemente solche Elemente dar. Fügen Sie daher den folgenden Programmcode der Show() und Hide() Methoden der CContextMenu Klasse hinzu.

//+------------------------------------------------------------------+
//| Zeigt das Kontextmenü                                            |
//+------------------------------------------------------------------+
void CContextMenu::Show(void)
  {
//--- Verlassen, falls das Element bereits sichtbar ist
//--- Anzeigen der Objekte des Kontextmenüs
//--- Anzeigen der Menüpunkt
//--- Den Status eines sichtbaren Elementes zuweisen
//--- Status des Kontextmenüs
//--- Registrieren des Status in dem vorherigen Knoten.
//--- Blockieren der Form

//--- Ein Signal für das Zurücksetzen der Prioritäten der linken Maustaste senden
   ::EventChartCustom(m_chart_id,ON_ZERO_PRIORITIES,CElement::Id(),0.0,"");
  }
//+-----------------------------------------------------------------+
//| Versteckt das Kontextmenü                                       |
//+-----------------------------------------------------------------+
void CContextMenu::Hide(void)
  {
//--- Abbrechen, falls das Element versteckt ist
//--- Verstecken der Objekte des Kontextmenüs
//--- Verstecke die Menüelemente
//--- Den Fokus löschen
//--- Status des Kontextmenüs
//--- Registrieren des Status in dem vorherigen Knoten.

//--- Sende ein Signal zum Wiederherstellen der Prioritäten der linken Maustaste
   ::EventChartCustom(m_chart_id,ON_SET_PRIORITIES,0,0.0,"");
  }

Wir können diese Nachrichten in der Hauptklasse für die Bearbeitung aller Nachrichten empfangen (CWndEvents). Dafür schreiben wir für jeden Bezeichner separate Bearbeitungsmethoden. Diese Methoden werden in der Hauptmethode für das Behandeln von benutzerdefinierten Events CWndEvents::ChartEventCustom() aufgerufen. 

class CWndEvents : public CWndContainer
  {
private:
   //--- Zurücksetzen der Prioritäten eines Klicks mit der linken Maustaste
   bool              OnZeroPriorities(void);
   //--- Wiederherstellung der Prioritäten eines Clicks mit der linken Maustaste
   bool              OnSetPriorities(void);
  };
//+------------------------------------------------------------------+
//| CHARTEVENT_CUSTOM event                                          |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventCustom(void)
  {
//--- Falls es sich um ein Signal zur Minimierung des Formulars handelt
//--- Falls es sich um ein Signal für die Maximierung des Formulars handelt
//--- Falls es sich um ein Signal zum Verstecken des Kontextmenüs unterhalb des auslösenden Menüelementes handelt
//--- Falls es sich um ein Signal für das Verstecken aller Kontextmenüs handelt
//--- Falls es sich um ein Signal zum Öffnen eines Dialogfensters handelt
//--- Falls es sich um ein Signal zum Schließen eines Dialogs Fensters handelt
//--- Falls es sich um ein Signal handelt, die Farben alle Elemente eines angegebene Formulars zurückzusetzen

//--- Wenn es sich um ein Signal zum Zurücksetzen der Prioritäten der linken Maustaste handelt
   if(OnZeroPriorities())
      return;
//--- Wenn es sich um ein Signal zum Wiederherstellen der Prioritäten der linken Maustaste handelt
   if(OnSetPriorities())
      return;
  }

In der CWndEvents::OnZeroPriorities() Methode, durchlaufen wir alle Elemente des aktiven Fensters und setzen die Prioritäten aller Elemente zurück, mit Ausnahme des Elementes, zu welchem der Element-Bezeichner aus der Nachricht passt (lparam-parameter) Und mit Aufnahme von Menü- und Kontextmenü-Elementen. Der Grund warum wir Menü- und Kontextmenü-Elemente ausschließen ist, dass es gleichzeitig mehrere geöffnete Menüelemente geben kann (Ein Element wurde durch ein anderes geöffnet).

//+------------------------------------------------------------------+
//| ON_ZERO_PRIORITIES event                                         |
//+------------------------------------------------------------------+
bool CWndEvents::OnZeroPriorities(void)
  {
//--- Wenn es sich um ein Signal zum Zurücksetzen der Prioritäten eines Klicks mit der linken Maustaste handelt
   if(m_id!=CHARTEVENT_CUSTOM+ON_ZERO_PRIORITIES)
      return(false);
//---
   int elements_total=CWndContainer::ElementsTotal(m_active_window_index);
   for(int e=0; e<elements_total; e++)
     {
      //--- Zurücksetzen der Prioritäten aller Elemente mit Ausnahme des Elementes zu welchem die ID passt...
      if(m_lparam!=m_wnd[m_active_window_index].m_elements[e].Id())
        {
         //--- ... mit Ausnahme der Kontextmenüs
         if(m_wnd[m_active_window_index].m_elements[e].ClassName()=="CMenuItem" ||
            m_wnd[m_active_window_index].m_elements[e].ClassName()=="CContextMenu")
            continue;
         //---
         m_wnd[m_active_window_index].m_elements[e].ResetZorders();
        }
     }
//---
   return(true);
  }

Falls die empfangene Nachricht den ON_SET_PRIORITIES Bezeichner enthält, dann werden die Prioritäten der linken Maustaste für alle Elemente des aktiven Fensters wiederhergestellt. 

//+------------------------------------------------------------------+
//| ON_SET_PRIORITIES event                                          |
//+------------------------------------------------------------------+
bool CWndEvents::OnSetPriorities(void)
  {
//--- Wenn es sich um ein Signal zum Wiederherstellen der Prioritäten der linken Maustaste handelt
   if(m_id!=CHARTEVENT_CUSTOM+ON_SET_PRIORITIES)
      return(false);
//---
   int elements_total=CWndContainer::ElementsTotal(m_active_window_index);
   for(int e=0; e<elements_total; e++)
      m_wnd[m_active_window_index].m_elements[e].SetZorders();
//---
   return(true);
  }

 


Schlussfolgerung

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

Abbildung  2. Die Struktur der Bibliothek zum aktuellen Stand der Entwicklung.

Abbildung 2. Die Struktur der Bibliothek zum aktuellen Stand der Entwicklung.

 

Dieses ist der abschließende Artikel des vierten Teils dieser Serie über grafische Interfaces. In dem ersten Kapitel dieses Teils, haben wir die Statusbar und das Tooltip-Interface Element realisiert. In dem zweiten Kapitel haben wir den Multi-Window-Modus und ein System für die Priorität der linken Maustaste besprochen.

Sie können das Material des ersten Teils dieser Serie über die angehängten Dateien 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 Artikel (Kapitel) des vierten Teils: