Graphisches Interface XI: Gezeichnete Steuerelemente (build 14.2)

Anatoli Kazharski | 24 August, 2017


Inhalt

Einführung

Der erste Artikel Grafische Interfaces I: Vorbereitung der Bibliotheksstruktur (Kapitel 1) beschreibt im Detail den Zweck der Bibliothek. Die Vollversion der Bibliothek im aktuellen Entwicklungszustand befindet sich immer am Ende eines jeden Artikels dieser Serie. Die Dateien müssen in die gleichen Verzeichnisse wie im beigefügten Archiv kopiert werden.

In der aktuellen Version der Bibliothek werden alle Steuerelemente als eigenständige Grafikobjekte des Typs OBJ_BITMAP_LABEL gezeichnet. Darüber hinaus beschreiben wir die fortgeführte grundsätzliche Optimierung des Codes der Bibliothek. Die Beschreibung hat im vorherigen Artikel begonnen. Wenden wir uns jetzt den Änderungen der Kernklassen der Bibliothek zu. Diese neue Version der Bibliothek ist jetzt noch objektorientierter. Der Code wurde auch leichter verständlich. Das hilft den Nutzern, die Bibliothek nach den eigenen Notwendigkeiten zu verwenden. 


Methoden zum Zeichnen der Steuerelemente

Eine Instanz der Klasse für den Hintergrund wurde in der Klasse CElement deklariert. Dessen Methoden können ein Objekt zum Zeichnen erstellen und löschen. Falls nötig kann deren Pointer abgefragt werden.

class CElement : public CElementBase
  {
protected:
   //--- Hintergrund eines Steuerelementes
   CRectCanvas       m_canvas;
   //---
public:
   //--- Rückgabe des Pointers auf den Hintergrund des Steuerelementes
   CRectCanvas      *CanvasPointer(void) { return(::GetPointer(m_canvas)); }
  };

Jetzt gibt es eine allgemeine Methode für das Erstellen eines Objektes (Hintergrund) zur Darstellung in einem Steuerelement. Sie befindet sich in der Basisklasse CElement und kann von allen Klassen der Steuerelemente in der Bibliothek verwendet werden. Mit der CElement::CreateCanvas() wird das Grafikobjekt dieses Typs erstellt. Als Argumente müssen (1) der Name, (2) die Koordinaten, (3) die Dimensionen und (4) das Format der Farbe übergeben werden. Das Standardformat ist COLOR_FORMAT_ARGB_NORMALIZE, das das Steuerelement transparent macht. Falls ungültige Dimensionen übergeben werden, werden sie zu Beginn der Methode korrigiert. Ist das Objekt erstellt und auf dem Chart einer MQL-Anwendung platziert, werden die Basiseigenschaften bestimmt, die vorher von allen Klassen des Steuerelementes übernommen werden.

class CElement : public CElementBase
  {
public:
   //--- Erstellen des Hintergrunds
   bool              CreateCanvas(const string name,const int x,const int y,
                                  const int x_size,const int y_size,ENUM_COLOR_FORMAT clr_format=COLOR_FORMAT_ARGB_NORMALIZE);
  };
//+------------------------------------------------------------------+
//| Erstellen des Hintergrunds zum Zeichnen eines Steuerelementes    |
//+------------------------------------------------------------------+
bool CElement::CreateCanvas(const string name,const int x,const int y,
                            const int x_size,const int y_size,ENUM_COLOR_FORMAT clr_format=COLOR_FORMAT_ARGB_NORMALIZE)
  {
//--- Anpassen der Größe
   int xsize =(x_size<1)? 50 : x_size;
   int ysize =(y_size<1)? 20 : y_size;
//--- Rücksetzen der Fehlervariablen
   ::ResetLastError();
//--- Erstellen eines Objektes
   if(!m_canvas.CreateBitmapLabel(m_chart_id,m_subwin,name,x,y,xsize,ysize,clr_format))
     {
      ::Print(__FUNCTION__," > Failed to create a canvas for drawing the control ("+m_class_name+"): ",::GetLastError());
      return(false);
     }
//--- Rücksetzen der Fehlervariablen
   ::ResetLastError();
//--- Abfragen des Pointers zur Basisklasse
   CChartObject *chart=::GetPointer(m_canvas);
//--- Starten auf dem Chart
   if(!chart.Attach(m_chart_id,name,(int)m_subwin,(int)1))
     {
      ::Print(__FUNCTION__," > Failed to attach the canvas for drawing to the chart: ",::GetLastError());
      return(false);
     }
//--- Eigenschaften
   m_canvas.Tooltip("\n");
   m_canvas.Corner(m_corner);
   m_canvas.Selectable(false);
//--- Alle Steuerelemente außer Formulare haben eine höhere Priorität als das Hauptsteuerelement
   Z_Order((dynamic_cast<CWindow*>(&this)!=NULL)? 0 : m_main.Z_Order()+1);
//--- Koordinaten
   m_canvas.X(x);
   m_canvas.Y(y);
//--- Größe
   m_canvas.XSize(x_size);
   m_canvas.YSize(y_size);
//--- Abstand vom Ankerpunkt
   m_canvas.XGap(CalculateXGap(x));
   m_canvas.YGap(CalculateYGap(y));
   return(true);
  }

Kommen wir nun zu den Basismethoden für das Zeichnen der Steuerelemente. Sie befinde sich alle in der Klasse CElement und sind als virtuell deklariert. 

Zu aller erst kommt das Zeichnen des Hintergrundes. In der Basisversion ist es nur ein einfaches einfärben durch die Methode CElement::DrawBackground(). Falls nötig kann eine Transparenz angeschaltet werden. Dafür wird die Methode CElement::Alpha() verwendet, der ein Wert für den Alphakanal zwischen 0 und 255 übergeben wird. Ein Wert von Null bedeutet volle Transparenz. In der aktuellen Version können nur dem Hintergrund und den Rändern eine Transparenz zugewiesen werden. Texte und Bilder bleiben komplett undurchsichtig und klar bei jedem Wert des Alphakanals.

class CElement : public CElementBase
  {
protected:
   //--- Werte des Alphakanals (Transparenz des Steuerelementes)
   uchar             m_alpha;
   //---
public:
   //--- Werte des Alphakanals (Transparenz des Steuerelementes)
   void              Alpha(const uchar value)                        { m_alpha=value;                   }
   uchar             Alpha(void)                               const { return(m_alpha);                 }
   //---
protected:
   //--- Zeichnen des Hintergrundes
   virtual void      DrawBackground(void);
  };
//+------------------------------------------------------------------+
//| Zeichnen des Hintergrundes                                       |
//+------------------------------------------------------------------+
void CElement::DrawBackground(void)
  {
   m_canvas.Erase(::ColorToARGB(m_back_color,m_alpha));
  }

Es ist oft notwendig, einen Rahmen für ein bestimmtes Steuerelement zeichnen. Die Methode CElement::DrawBorder() zeichnet um die Kanten eines bestimmten Hintergrundobjektes einen Rahmen. Die Methode Rectangle() kann auch zu diesem Zweck verwendet werden. Sie zeichnet ein Rechteck ohne Füllung.

class CElement : public CElementBase
  {
protected:
   //--- Zeichnen des Rahmen
   virtual void      DrawBorder(void);
  };
//+------------------------------------------------------------------+
//| Zeichnen der Rahmen                                              |
//+------------------------------------------------------------------+
void CElement::DrawBorder(void)
  {
//--- Koordinaten
   int x1=0,y1=0;
   int x2=m_canvas.X_Size()-1;
   int y2=m_canvas.Y_Size()-1;
//--- Zeichnen des Rechtecks ohne Füllung
   m_canvas.Rectangle(x1,y1,x2,y2,::ColorToARGB(m_border_color,m_alpha));
  }

Im vorherigen Artikel ist bereits erwähnt worden, dass eine beliebige Zahl von Bildergruppen jedem Steuerelement zugewiesen werden kann. Daher muss die Zeichenmethode des Steuerelementes alle Bilder des Nutzers darstellen können. Diesen Zweck erfüllt die Methode CElement::DrawImage(). Das Programm iteriert darin sequentiell über alle Gruppen und Bilder, um sie Pixel für Pixel auf den Hintergrund zu zeichnen. Bevor aber die Schleife für die Darstellung der Bilder beginnt, wird das aktuelle ausgewählte Bild in der Gruppe bestimmt. Hier ist der Code dieser Methode:

class CElement : public CElementBase
  {
protected:
   //--- Zeichnen des Bildes
   virtual void      DrawImage(void);
  };
//+------------------------------------------------------------------+
//| Zeichnen des Bildes                                              |
//+------------------------------------------------------------------+
void CElement::DrawImage(void)
  {
//--- Anzahl der Gruppen
   uint group_total=ImagesGroupTotal();
//--- Zeichnen des Bildes
   for(uint g=0; g<group_total; g++)
     {
      //--- Index des ausgewählten Bildes
      int i=SelectedImage(g);
      //--- Wenn es kein Bild gibt
      if(i==WRONG_VALUE)
         continue;
      //--- Koordinaten
      int x =m_images_group[g].m_x_gap;
      int y =m_images_group[g].m_y_gap;
      //--- Größe
      uint height =m_images_group[g].m_image[i].Height();
      uint width  =m_images_group[g].m_image[i].Width();
      //--- Zeichnen
      for(uint ly=0,p=0; ly<height; ly++)
        {
         for(uint lx=0; lx<width; lx++,p++)
           {
            //--- Gibt es keine Farbe, gehe zum nächsten Pixel
            if(m_images_group[g].m_image[i].Data(p)<1)
               continue;
            //--- Ermitteln der Farbe der unteren Schicht (Zellhintergrund) und der Farbe des jew. Pixels des Icons
            uint background  =::ColorToARGB(m_canvas.PixelGet(x+lx,y+ly));
            uint pixel_color =m_images_group[g].m_image[i].Data(p);
            //--- Mischen der Farben
            uint foreground=::ColorToARGB(m_clr.BlendColors(background,pixel_color));
            //--- Zeichnen der Pixel des darüber gelegten Icons
            m_canvas.PixelSet(x+lx,y+ly,foreground);
           }
        }
     }
  }

Viele Steuerelemente haben einen beschreibenden Text. Er wird mit der Methode CElement::DrawText() dargestellt. Mehrere Variablen in dieser Mehrere erlauben, den angezeigten Text abhängig vom Zustand des Steuerelementes anzupassen. Es gibt drei Zustände für die Steuerelemente:

  • gesperrt;
  • gedrückt;
  • im Fokus (Maus schwebt darüber).

Zusätzlich berücksichtigt die Methode, wenn der Modus der Textausrichtung zentriert ist. Das ist deren Code:

class CElement : public CElementBase
  {
protected:
   //--- Textausgabe
   virtual void      DrawText(void);
  };
//+------------------------------------------------------------------+
//| Textausgabe                                                      |
//+------------------------------------------------------------------+
void CElement::DrawText(void)
  {
//--- Koordinaten
   int x =m_label_x_gap;
   int y =m_label_y_gap;
//--- Definieren der Textfarbe der Kennzeichnung
   color clr=clrBlack;
//--- Wenn das Steuerelement gesperrt ist
   if(m_is_locked)
      clr=m_label_color_locked;
   else
     {
      //--- Wenn eine Taste gedrückt wurde
      if(!m_is_pressed)
         clr=(m_mouse_focus)? m_label_color_hover : m_label_color;
      else
        {
         if(m_class_name=="CButton")
            clr=m_label_color_pressed;
         else
            clr=(m_mouse_focus)? m_label_color_hover : m_label_color_pressed;
        }
     }
//--- Eigenschaften des Schrifttyps
   m_canvas.FontSet(m_font,-m_font_size*10,FW_NORMAL);
//--- Zeichnen des Textes unter Berücksichtigung einer zentrierten Textausrichtung
   if(m_is_center_text)
     {
      x =m_x_size>>1;
      y =m_y_size>>1;
      m_canvas.TextOut(x,y,m_label_text,::ColorToARGB(clr),TA_CENTER|TA_VCENTER);
     }
   else
      m_canvas.TextOut(x,y,m_label_text,::ColorToARGB(clr),TA_LEFT);
  }

Alle oben erwähnten Methoden werden von der virtuellen 'public' Methode CElement::Draw() aufgerufen. Sie hat keinen Basiscode, da die Methoden zum Zeichnen in jedem Steuerelement individuell sind.

class CElement : public CElementBase
  {
public:
   //--- Zeichnen des SteuerelementesDraws the control
   virtual void      Draw(void) {}
  };

Betrachten wir die Methode CElement::Update(). Sie wird jedes Mal aufgerufen, wenn das Steuerelement des grafischen Interfaces geändert wird. Zwei Optionen des Aufrufes sind möglich: (1) das Steuerelement komplett neuzeichnen oder (2) die Umsetzung vorheriger Änderungen (siehe im unten aufgelisteten Code). Auch diese Methode ist als virtuell deklariert, da die Klassen bestimmter Steuerelemente individuelle Lösungen bedürfen, die die Besonderheiten und die Reihenfolge des Zeichnens berücksichtigen.

class CElement : public CElementBase
  {
public:
   //--- Aktualisieren des Steuerelementes zur Anzeige der letzten Änderungen
   virtual void      Update(const bool redraw=false);
  };
//+------------------------------------------------------------------+
//| Aktualisieren des Steuerelementes                                |
//+------------------------------------------------------------------+
void CElement::Update(const bool redraw=false)
  {
//--- Neuzeichnen des Steuerelementes
   if(redraw)
     {
      Draw();
      m_canvas.Update();
      return;
     }
//--- Apply
   m_canvas.Update();
  }


Das neue Design des grafischen Interfaces

Da jetzt alle Steuerelemente gezeichnet werden, ist es möglich das grafische Interface in einem neuen Design zu zeigen. Es keinen Grund irgendetwas Spezielles neu zu erfinden, es kann eine fertige Lösung verwendet werden. Die lakonische Ästhetik von Windows 10 wurde als Basis verwendet. 

Bilder von Icons in solchen Elementen wie Formulartasten für Steuerelemente, Optionstasten, Kontrollkästchen, Menüelemente, Elemente von baumartigen Listen und anderes wurden Windows 10 angeglichen. 

Wie bereits erwähnt kann die Transparenz jetzt für jedes Steuerelement bestimmt werden. Das Bildschirmfoto unten zeigt das Beispiel eines durchsichtigen Fensters (CWindow). Der Wert des Alphakanals beträgt hier 200.  

Fig. 8. Demonstration der Transparenz eines Formulars eins Steuerelementes. 

Fig. 8. Demonstration der Transparenz eines Formulars eins Steuerelementes.


Mit der Methode CWindow::TransparentOnlyCaption() kann der gesamte Bereich des Formulars transparent gemacht werden. Standardmäßig sind nur die Kopfzeilen transparent

class CWindow : public CElement
  {
private:
   //--- Ermöglichen der Transparenz nur für die Header
   bool              m_transparent_only_caption;
   //---
public:
   //--- Ermöglichen der Transparenz nur für die Header
   void              TransparentOnlyCaption(const bool state) { m_transparent_only_caption=state; }
  };
//+------------------------------------------------------------------+
//| Konstruktor                                                      |
//+------------------------------------------------------------------+
CWindow::CWindow(void) : m_transparent_only_caption(true)
  {
...
  }

Unten sind verschiedene Arten von Tasten dargestellt:

 Fig. 9. Demonstration der Darstellung verschiedener Arten von Tasten.

Fig. 9. Demonstration der Darstellung verschiedener Arten von Tasten.


Das nächste Bildschirmfotos zeigt das aktuelle Aussehen der Kontrollkästchen, Spin-Bearbeitungsfelder, Kombinationsfelder mit eine Auswahlliste und numerischen Schieberegler. Beachten Sie bitte, jetzt können Sie animierte Icons verwenden. Das dritte Element der Statuszeile imitiert eine Verbindungsunterbrechung zum Server. Dessen Aussehen ist eine genaue Kopie ähnlicher Elemente in der Statuszeile des MetaTrader 5.

Fig. 10. Demonstration des Aussehens von Kontrollkästchen, Kombinationsfelder, Schieberegler und anderer Elemente.

Fig. 10. Demonstration des Aussehens von Kontrollkästchen, Kombinationsfelder, Schieberegler und anderer Elemente.

Das Aussehen der anderen Steuerelemente des grafischen Interfaces dieser Bibliothek zeigt die MQL-Anwendung, die diesem Artikel beigefügt ist.


Tooltips

Zusätzliche Methoden, um die Darstellung von Tooltips in den Steuerelementen zu handhaben, wurde der Klasse CElement hinzugefügt. Jetzt kann ein standardmäßiger Tooltip jedem Steuerelement zugewiesen werden, wenn dessen Text nicht mehr als 63 Buchstaben aufweist. Mit der Methode CElement::Tooltip() kann der Text eines Tooltips bestimmt und abgefragt werden.

class CElement : public CElementBase
  {
protected:
   //--- Tooltip Text
   string            m_tooltip_text;
   //---
public:
   //--- Tooltip
   void              Tooltip(const string text)                      { m_tooltip_text=text;             }
   string            Tooltip(void)                             const { return(m_tooltip_text);          }
  };

Die Methode CElement::ShowTooltip() ermöglicht oder verhindert die Anzeige des Tooltips. 

class CElement : public CElementBase
  {
public:
   //--- Tooltip Anzeigemodus
   void              ShowTooltip(const bool state);
  };
//+------------------------------------------------------------------+
//| Bestimmen des Anzeigemodus des Tooltips                          |
//+------------------------------------------------------------------+
void CElement::ShowTooltip(const bool state)
  {
   if(state)
      m_canvas.Tooltip(m_tooltip_text);
   else
      m_canvas.Tooltip("\n");
  }

Jede Klasse der Steuerelemente hat Methoden, um die Pointer auf verschachtelte Steuerelemente abzufragen. Wenn es zum Beispiel nötig ist, Tootips für Tasten zu erstellen, dann sollten die folgenden Codezeilen der Methode zum Erstellen des Formulars in der Klasse des Nutzers hinzugefügt werden:

...
//--- Bestimmen des Tooltips
   m_window.GetCloseButtonPointer().Tooltip("Close");
   m_window.GetCollapseButtonPointer().Tooltip("Collapse/Expand");
   m_window.GetTooltipButtonPointer().Tooltip("Tooltips");
...

Sehen Sie, wie es im Bild unten aussieht. Die standardmäßigen Tooltips wurden für die Tasten aktiviert. Steuerelemente, die eine Beschreibung länger als 63 Buchstaben benötigen, müssen das Steuerelement CTooltip verwenden.

 Fig. 11. Demonstration der zwei Arten von Tooltips (standardmäßig und nutzerversion).

Fig. 11. Demonstration der zwei Arten von Tooltips (standardmäßig und nutzerversion).



Die Identifikatoren für neue Ereignisse

Jetzt gibt es auch Identifikatoren für neue Ereignisse. Das reduziert die CPU-Last signifikant. Wie wurde das erreicht?

Beim Erstellen einer größeren MQL-Anwendung mit einem grafischen Interface und einer Vielzahl von Steuerelementen ist es wichtig, auf eine minimale CPU-Last zu achten.

Wenn die Maus über einem Steuerelement ist, wird es hervorgehoben. Das signalisiert, dass das Steuerelement für eine Interaktion bereit ist. Allerdings sind nicht immer zur gleichen Zeit alle Steuerelemente bereit und sichtbar.

  • Auswahllisten, Kalender und Kontextmenüs sind die meiste Zeit unsichtbar. Sie werden nur gelegentlich zur Auswahl der vom Nutzer gewünschten Option, Datums oder Modus geöffnet.
  • Gruppen von Steuerelementen können unterschiedlichen Karteireitern zugewiesen werden, aber es ist immer nur einer geöffnet. 
  • Wenn das Formular minimiert ist, werden alle seine Steuerelemente auch ausgeblendet.
  • Falls ein Dialogfeld offen ist, dann kann nur dieses auf die Ereignisse reagieren.

Natürlich muss nicht immer die gesamte Liste der Steuerelemente eines grafischen Interfaces abgearbeitet werden, wenn nur einige wenige bereit zur Verwendung sind. Es muss ein Array nur für die Ereignisse erstellt werden, die Teil der Liste der geöffneten Steuerelemente sind.

Es gibt auch Steuerelemente mit Interaktionen, die nur die Steuerelemente selbst betreffen. Daher ist solch ein Steuerelement das einzige, das für die Bearbeitung verfügbar sein muss. Listen wir alle diese Steuerelemente mit den Situationen auf:

  • Bewegen des Schieberegler einer Bildlaufleiste (CScroll). Es muss die Bearbeitung nur für die Bildlaufleiste und das Steuerelement zu dem es gehört (Listenansicht, Tabelle, mehrzeiliges Texteingabefeld etc.) ermöglicht werden. 
  • Bewegung des Schiebereglers (CSlider). Dafür muss nur der Schieberegler und das Spin-Bearbeitungsfeld bereits sein, über die die Wertänderungen entstehen.
  • Ändern der Spaltenbreite einer Tabelle (CTable). Nur die Tabelle muss zur Änderung bereit stehen.
  • Ändern der Listenbreite einer Baumansicht (CTreeView). Nur dieses Steuerelement muss bearbeitet werden, wenn der angrenzende Rahmen verändert wird.
  • Verschieben des Formulars (CWindow). Alles außer dem bewegten Formular wird von einer Bearbeitung ausgeschlossen.

In allen aufgelisteten Fällen müssen die Steuerelemente Nachrichten senden, die vom Kern der Bibliothek empfangen und verarbeitet werden müssen. Der Kern bearbeitet zwei Identifikatoren von Ereignissen, um die Verfügbarkeit von Steuerelementen zu bestimmen (ON_SET_AVAILABLE) und erzeugt ein Array der Steuerelemente (ON_CHANGE_GUI). Alle Identifikatoren von Ereignissen sind in der Datei Define.mqh gespeichert:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
...
#define ON_CHANGE_GUI               (28) // Das grafische Interface wurde geändert
#define ON_SET_AVAILABLE            (39) // Setze die verfügbaren Elemente
...

Das Ein- und Ausblenden der Steuerelemente bewirken die Methoden Show() und Hide(). Eine neue Eigenschaft wurde der Klasse CElement zum Bestimmen der Verfügbarkeit hinzugefügt. Dessen Wert wird mit der virtuellen 'public' Methode CElement::IsAvailable() bestimmt. Hier, ähnlich wie bei anderen Methoden, die den Zustand eines Steuerelementes festlegen, wird der Wert auch den verschachtelten Steuerelementen übergeben. Die Prioritäten eines Klicks der linken Maustaste werden relativ zum übergebenen Zustand festgelegt. Wenn das Steuerelement nicht verfügbar ist, werden die Prioritäten zurückgesetzt.

class CElement : public CElementBase
  {
protected:
   bool              m_is_available;   // availability
   //---
public:
   //--- Zeichen der Verfügbarkeit des Steuerelementes
   virtual void      IsAvailable(const bool state)                   { m_is_available=state;                 }
   bool              IsAvailable(void)                         const { return(m_is_available);               }
  };
//+------------------------------------------------------------------+
//| Verfügbarkeit des Steuerelementes                                |
//+------------------------------------------------------------------+
void CElement::IsAvailable(const bool state)
  {
//--- Verlassen, wenn bereits alles gesetzt
   if(state==CElementBase::IsAvailable())
      return;
//--- Setzen
   CElementBase::IsAvailable(state);
//--- Andere Steuerelemente
   int elements_total=ElementsTotal();
   for(int i=0; i<elements_total; i++)
      m_elements[i].IsAvailable(state);
//--- Setzen der Prioritäten des linken Mausklicks
   if(state)
      SetZorders();
   else
      ResetZorders();
  }

Als Beispiel ist der Code der Methode CComboBox::ChangeComboBoxListState() aufgeführt, die die Sichtbarkeit des Steuerelementes einer Auswahlliste in einem Kombinationsfeld bestimmt. 

Wird eine Taste eines Kombinationsfeldes gedrückt, und es soll die Listenansicht gezeigt werden, dann wird das Ereignis mit dem Identifikator ON_SET_AVAILABL sofort nach der Darstellung der Listenansicht gesendet. Als weitere Parameter werden (1) der Identifikator des Steuerelementes und (2) der Merker der benötigten Aktion der Ereignisbehandlung übergeben: Wiederherstellen aller sichtbaren Elemente oder nur das Steuerelement verfügbar machen, das dem Identifikator des Ereignisses des Steuerelementes entspricht. Ein Merker mit dem Wert 1 bedeutet Wiederherstellung, während ein Wert von 0 die Verfügbarkeit des angegebenen Steuerelementes bestimmt. 

Der Nachricht mit dem Identifikator ON_SET_AVAILABLE folgt eine Nachricht mit der Identifikator des Ereignisses ON_CHANGE_GUI. Die Bearbeitung dessen führt zum Erstellen eines Arrays mit den aktuell verfügbaren Steuerelementen.

//+------------------------------------------------------------------+
//| Ändern des akt. Zustands des Kombinationsfeldes ins Gegenteil    |
//+------------------------------------------------------------------+
void CComboBox::ChangeComboBoxListState(void)
  {
//--- Falls eine Taste gedrückt wurde
   if(m_button.IsPressed())
     {
      //--- Zeigen der Listenansicht
      m_listview.Show();
      //--- Senden eine Nachricht der verfügbaren Steuerelemente
      ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),0,"");
      //--- Senden einer Nachricht über die Veränderung des grafischen Interfaces
      ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,"");
     }
   else
     {
      //--- Ausblenden der Listenansicht
      m_listview.Hide();
      //--- Senden einer Nachricht die Steuerelemente wiederherzustellen
      ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),1,"");
      //--- Senden einer Nachricht über die Veränderung des grafischen Interfaces
      ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,"");
     }
  }

Aber für die Karteireiter zum Beispiel genügt es, nur eine der beschriebenen Ereignisses zur Verarbeitung zu senden, die mit dem Identifikator ON_CHANGE_GUI. Keine Steuerelemente müssen deswegen verfügbar gemacht werden. Beim Wechsel der Karteireiter wird die Sichtbarkeit des Steuerelementes verwendet, die der Gruppe der Karteireiter zugewiesen worden ist. In der Klasse CTabs wird die Sichtbarkeit der Gruppe der Steuerelemente durch die Methode CTabs::ShowTabElements() verwaltet: etwas das sich in der neuen Version der Bibliothek geändert hat. Es könnte manchmal notwendig sein, Gruppen von Karteireiter innerhalb eines Karteireiters zu platzieren. Daher, selbst wenn die Darstellung der Steuerelemente des gewählten Karteireiters zeigt, dass einer von ihnen des Typs CTabs ist, dann wird sofort die Methode CTabs::ShowTabElements() in diesem Steuerelement aufgerufen. Dieser Ansatz erlaubt das Platzieren von Karteireiter auf jeder verschachtelten Ebene.

//+------------------------------------------------------------------+
//| Zeigen nur der Steuerelemente des gew. Karteireiters             |
//+------------------------------------------------------------------+
void CTabs::ShowTabElements(void)
  {
//--- Verlassen, wenn die Karteireiter ausgeblendet sind
   if(!CElementBase::IsVisible())
      return;
//--- Prüfen des Index des gewählten Karteireiters
   CheckTabIndex();
//---
   uint tabs_total=TabsTotal();
   for(uint i=0; i<tabs_total; i++)
     {
      //--- Abfragen der Anzahl der Steuerelemente eines Karteireiters
      int tab_elements_total=::ArraySize(m_tab[i].elements);
      //--- Wenn dieser Karteireiter ausgewählt wurde
      if(i==m_selected_tab)
        {
         //--- Anzeigen des des Karteireiters
         for(int j=0; j<tab_elements_total; j++)
           {
            //--- Anzeigen des Steuerelementes
            CElement *el=m_tab[i].elements[j];
            el.Reset();
            //--- Wenn dies der Karteireiter ist, zeige die Steuerelemente des Geöffneten
            CTabs *tb=dynamic_cast<CTabs*>(el);
            if(tb!=NULL)
               tb.ShowTabElements();
           }
        }
      //--- Ausblenden der Steuerelemente ausgeblendeter Karteireiter
      else
        {
         for(int j=0; j<tab_elements_total; j++)
            m_tab[i].elements[j].Hide();
        }
     }
//--- Senden einer Nachricht darüber
   ::EventChartCustom(m_chart_id,ON_CLICK_TAB,CElementBase::Id(),m_selected_tab,"");
  }

Sind jetzt die Steuerelemente des gewählten Karteireiters gezeigt, sendet die Methode eine Nachricht, dass das grafische Interface geändert wurde, und es ist notwendig, einen Array mit den zur Bearbeitung verfügbaren Steuerelementen zu erstellen.

//+------------------------------------------------------------------+
//| Tastendruck auf einen Karteireiter einer Gruppe                  |
//+------------------------------------------------------------------+
bool CTabs::OnClickTab(const int id,const int index)
  {
//--- Verlassen, wenn (1) der Identifikator nicht übereinstimmt oder (2) das Steuerelement blockiert ist
   if(id!=CElementBase::Id() || CElementBase::IsLocked())
      return(false);
//--- Verlassen, wenn der Index nicht übereinstimmt
   if(index!=m_tabs.SelectedButtonIndex())
      return(true);
//--- Sichern des Index des gewählten Karteireiters
   SelectedTab(index);
//--- Neuzeichnen des Steuerelementes
   Reset();
   Update(true);
//--- Zeigen nur der Steuerelemente des gew. Karteireiters
   ShowTabElements();
//--- Senden einer Nachricht über die Veränderung des grafischen Interfaces
   ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0.0,"");
   return(true);
  }

Zwei neue Identifikatoren für Ereignisse wurden der Datei Defines.mqh hinzugefügt.

  • ON_MOUSE_FOCUS — der Mauskursor erreicht den Bereich des Steuerelementes;
  • ON_MOUSE_BLUR — der Mauskursor verlässt den Bereich des Steuerelementes.
...
#define ON_MOUSE_BLUR               (34) // Mauskursor verlässt das Steuerelement
#define ON_MOUSE_FOCUS              (35) // Mauskursor erreicht das Steuerelement
...

Diese Ereignisse werden nur erzeugt, wenn die Grenzen der Steuerelemente überschritten werden. Die Basisklasse der Steuerelemente (CElementBase) enthält die Methode CElementBase::CheckCrossingBorder(), die den Moment bestimmt, wenn die Maus die Grenze überschreitet. Ergänzen wir noch die Erzeugung der oben beschriebenen Ereignisse:

//+------------------------------------------------------------------+
//| Prüfen der Grenzüberschreitung eines Steuerelementes             |
//+------------------------------------------------------------------+
bool CElementBase::CheckCrossingBorder(void)
  {
//--- Wenn jetzt die Grenze des Steuerelementes überschritten wurde
   if((MouseFocus() && !IsMouseFocus()) || (!MouseFocus() && IsMouseFocus()))
     {
      IsMouseFocus(MouseFocus());
      //--- Nachricht über den Eintritt in das Steuerelement
      if(MouseFocus())
         ::EventChartCustom(m_chart_id,ON_MOUSE_FOCUS,m_id,m_index,m_class_name);
      //--- Nachricht über den Austritt aus dem Steuerelement
      else
         ::EventChartCustom(m_chart_id,ON_MOUSE_BLUR,m_id,m_index,m_class_name);
      //---
      return(true);
     }
//---
   return(false);
  }

In der aktuellen Version der Bibliothek werden diese Ereignisse nur im Hauptmenü (CMenuBar) bearbeitet. Schauen wir uns an, wie es arbeitet.

Wenn das Hauptmenü einmal erstellt und gesichert ist, fallen dessen Elemente (CMenuItem) in die Speicherliste als eigenständige Steuerelemente. Die Klasse CMenuItem leitet sich ab von CButton (das Steuerelement für Tasten). Daher führt der Aufruf der Ereignisbehandlung des Menüelementes zuerst zu einem Aufruf der Ereignisbehandlung der Basisklasse CButton.

//+------------------------------------------------------------------+
//| Ereignisbehandlung                                               |
//+------------------------------------------------------------------+
void CMenuItem::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Ereignisbehandlung in der Basisklasse
   CButton::OnEvent(id,lparam,dparam,sparam);
...
  }

The base event handler already contains tracking of the button crossing, it does not need to be duplicated in the CMenuItem derived class.

//+------------------------------------------------------------------+
//| Ereignisbehandlung                                               |
//+------------------------------------------------------------------+
void CButton::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Ereignisbehandlung der Mausbewegung
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Neuzeichnen des Steuerelementes nach der Grenzüberschreitung
      if(CheckCrossingBorder())
         Update(true);
      //---
      return;
     }
...
  }

Wenn der Kursor die Grenze nach innen überschreitet, wird ein Ereignis mit dem Identifikator ON_MOUSE_FOCUS generiert. Jetzt verwendet die Ereignisbehandlung der Klasse CMenuBar dieses Ereignis, um zum Kontextmenü zu wechseln, wenn das Hauptmenü aktiv ist. 

//+------------------------------------------------------------------+
//| Ereignisbehandlung                                               |
//+------------------------------------------------------------------+
void CMenuBar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Ereignisbehandlung: wechseln des Fokus auf die Menüelemente
   if(id==CHARTEVENT_CUSTOM+ON_MOUSE_FOCUS)
     {
      //--- Verlassen, wenn (2) das Hauptmenü nicht aktiv ist oder (2) die Identifikatoren nicht passen
      if(!m_menubar_state || lparam!=CElementBase::Id())
         return;
      //--- Wechseln des Kontextmenü durch das aktive Element des Hauptmenüs
      SwitchContextMenuByFocus();
      return;
     }
...
  }


Optimierung des Kerns der Bibliothek

Betrachten wir die Änderungen, Korrekturen und Ergänzungen der Klassen CWndContainer und CWndEvents, die man als Kern der Bibliothek bezeichnen könnte. Beide organisieren den Zugriff auf all ihre Steuerelemente und verarbeiten den Strom der Ereignisse, die von den Steuerelementen des grafischen Interfaces erzeugt werden.

Für das bearbeiten von Arrays wurde eine Template-Methode CWndContainer::ResizeArray() der Klasse CWndContainer hinzugefügt. Ein Array beliebigen Typs, das dieser Methode übergeben wird, wird um Eins erhöht, und die Methode gibt den Index des letzten Elementes zurück.

//+------------------------------------------------------------------+
//| Klasse zum speichern aller Objekte des Interfaces                |
//+------------------------------------------------------------------+
class CWndContainer
  {
private:
   //--- Erhöhen des Arrays um ein Element und Rückgabe des letzten Index
   template<typename T>
   int               ResizeArray(T &array[]);
  };
//+------------------------------------------------------------------+
//| Erhöhen des Arrays um ein Element und Rückgabe des letzten Index |
//+------------------------------------------------------------------+
template<typename T>
int CWndContainer::ResizeArray(T &array[])
  {
   int size=::ArraySize(array);
   ::ArrayResize(array,size+1,RESERVE_SIZE_ARRAY);
   return(size);
  }

Erinnern wir uns, dass die privaten Arrays in der Struktur WindowElements für viele Steuerelemente in der Klasse CWndContainer (Speichern der Pointer auf alle Steuerelemente des grafischen Interfaces) deklariert wurde. Um die Anzahl der Steuerelemente eines bestimmten Typs einer Liste zu erhalten, wurde die Methode CWndContainer::ElementsTotal() erstellt. Übergeben wird der der Index des Fensters und der Typ des Steuerelementes, um deren Nummer in dem grafischen Interfaces der MQL-Anwendung zu erhalten. Eine neue Enumeration ENUM_ELEMENT_TYPE wurde der Datei Enums.mqh hinzugefügt, um die Typen von Steuerelementen festzulegen:

//+------------------------------------------------------------------+
//| Enumeration of the control types                                 |
//+------------------------------------------------------------------+
enum ENUM_ELEMENT_TYPE
  {
   E_CONTEXT_MENU    =0,
   E_COMBO_BOX       =1,
   E_SPLIT_BUTTON    =2,
   E_MENU_BAR        =3,
   E_MENU_ITEM       =4,
   E_DROP_LIST       =5,
   E_SCROLL          =6,
   E_TABLE           =7,
   E_TABS            =8,
   E_SLIDER          =9,
   E_CALENDAR        =10,
   E_DROP_CALENDAR   =11,
   E_SUB_CHART       =12,
   E_PICTURES_SLIDER =13,
   E_TIME_EDIT       =14,
   E_TEXT_BOX        =15,
   E_TREE_VIEW       =16,
   E_FILE_NAVIGATOR  =17,
   E_TOOLTIP         =18
  };

Der Code der Methode CWndContainer::ElementsTotal() lautet wie folgt:

//+------------------------------------------------------------------+
//| Nr. des Steuerelementes des Typs des angegebenen Fensterindex    |
//+------------------------------------------------------------------+
int CWndContainer::ElementsTotal(const int window_index,const ENUM_ELEMENT_TYPE type)
  {
//--- Prüfen der Arraygrenze
   int index=CheckOutOfRange(window_index);
   if(index==WRONG_VALUE)
      return(WRONG_VALUE);
//---
   int elements_total=0;
//---
   switch(type)
     {
      case E_CONTEXT_MENU    : elements_total=::ArraySize(m_wnd[index].m_context_menus);   break;
      case E_COMBO_BOX       : elements_total=::ArraySize(m_wnd[index].m_combo_boxes);     break;
      case E_SPLIT_BUTTON    : elements_total=::ArraySize(m_wnd[index].m_split_buttons);   break;
      case E_MENU_BAR        : elements_total=::ArraySize(m_wnd[index].m_menu_bars);       break;
      case E_MENU_ITEM       : elements_total=::ArraySize(m_wnd[index].m_menu_items);      break;
      case E_DROP_LIST       : elements_total=::ArraySize(m_wnd[index].m_drop_lists);      break;
      case E_SCROLL          : elements_total=::ArraySize(m_wnd[index].m_scrolls);         break;
      case E_TABLE           : elements_total=::ArraySize(m_wnd[index].m_tables);          break;
      case E_TABS            : elements_total=::ArraySize(m_wnd[index].m_tabs);            break;
      case E_SLIDER          : elements_total=::ArraySize(m_wnd[index].m_sliders);         break;
      case E_CALENDAR        : elements_total=::ArraySize(m_wnd[index].m_calendars);       break;
      case E_DROP_CALENDAR   : elements_total=::ArraySize(m_wnd[index].m_drop_calendars);  break;
      case E_SUB_CHART       : elements_total=::ArraySize(m_wnd[index].m_sub_charts);      break;
      case E_PICTURES_SLIDER : elements_total=::ArraySize(m_wnd[index].m_pictures_slider); break;
      case E_TIME_EDIT       : elements_total=::ArraySize(m_wnd[index].m_time_edits);      break;
      case E_TEXT_BOX        : elements_total=::ArraySize(m_wnd[index].m_text_boxes);      break;
      case E_TREE_VIEW       : elements_total=::ArraySize(m_wnd[index].m_treeview_lists);  break;
      case E_FILE_NAVIGATOR  : elements_total=::ArraySize(m_wnd[index].m_file_navigators); break;
      case E_TOOLTIP         : elements_total=::ArraySize(m_wnd[index].m_tooltips);        break;
     }
//--- Rückgabe der Nummer des Steuerelementes des angegebenen Typs
   return(elements_total);
  }

Um die CPU-Last zu reduzieren müssen mehrere Arrays der Struktur WindowElements hinzugefügt werden, die die Pointer der Steuerelemente der folgenden Kategorien aufnehmen.

  • Array der Hauptsteuerelemente
  • Array der Steuerelemente mit einem Timer
  • Array der Steuerelemente, die sichtbar und verfügbar zur Bearbeitung sind
  • Array der Steuerelemente mit aktivierter automatischer Größenanpassung entlang der X-Achse
  • Array der Steuerelemente mit aktivierter automatischer Größenanpassung entlang der Y-Achse
class CWndContainer
  {
protected:
...
   //--- Die Struktur der Kontroll-Arrays
   struct WindowElements
     {
      ...
      //--- Array der Hauptsteuerelemente
      CElement         *m_main_elements[];
      //--- Steuerelemente mit einem Timer
      CElement         *m_timer_elements[];
      //--- Steuerelemente die aktuell sichtbar und verfügbar sind
      CElement         *m_available_elements[];
      //--- Steuerelemente mit autom. Größenänderung entlang der X-Achse
      CElement         *m_auto_x_resize_elements[];
      //--- Steuerelemente mit autom. Größenänderung entlang der Y-Achse
      CElement         *m_auto_y_resize_elements[];
      ...
     };
   //--- Array der Arrays mit Steuerelemente für jedes Fenster
   WindowElements    m_wnd[];
...
  };

Die Größe dieser Arrays erhält man über die entsprechenden Methoden:

class CWndContainer
  {
public:
   //--- Die Zahl der Hauptsteuerelemente
   int               MainElementsTotal(const int window_index);
   //--- Die Zahl der Steuerelemente mit einem Timer
   int               TimerElementsTotal(const int window_index);
   //--- Die Zahl der Steuerelemente mit autom. Größenänderung entlang der X-Achse
   int               AutoXResizeElementsTotal(const int window_index);
   //--- Die Zahl der Steuerelemente mit autom. Größenänderung entlang der Y-Achse
   int               AutoYResizeElementsTotal(const int window_index);
   //--- Die Zahl der aktuell verfügbaren Steuerelemente
   int               AvailableElementsTotal(const int window_index);
  };

The CWndContainer::AddToElementsArray() method adds pointers to the array of main controls. Die Kurzversion dieser Methode:

//+------------------------------------------------------------------+
//| Hinzufügen von Pointer zum Array der Steuerelemente              |
//+------------------------------------------------------------------+
void CWndContainer::AddToElementsArray(const int window_index,CElementBase &object)
  {
...
//--- Hinzufügen zum Array des Hauptsteuerelementes
   last_index=ResizeArray(m_wnd[window_index].m_main_elements);
   m_wnd[window_index].m_main_elements[last_index]=::GetPointer(object);
...
  }

Arrays anderer Kategorien werden von der Klasse CWndEvents erstellt (siehe unten). Es werden spezielle Methoden für das Hinzufügen der Pointer werden verwendet.

class CWndContainer
  {
protected:
   //--- Hinzufügen der Pointer zum Array der Steuerelemente mit Timer
   void              AddTimerElement(const int window_index,CElement &object);
   //--- Hinzufügen der Pointer zum Array der Steuerelemente mit autom. Größenänderung entlang der X-Achse
   void              AddAutoXResizeElement(const int window_index,CElement &object);
   //--- Hinzufügen der Pointer zum Array der Steuerelemente mit autom. Größenänderung entlang der Y-Achse
   void              AddAutoYResizeElement(const int window_index,CElement &object);
   //--- Hinzufügen der Pointer zum Array der aktuell verfügbaren Steuerelemente
   void              AddAvailableElement(const int window_index,CElement &object);
  };
//+------------------------------------------------------------------+
//| Hinzufügen der Pointer zum Array der Steuerelemente mit Timer    |
//+------------------------------------------------------------------+
void CWndContainer::AddTimerElement(const int window_index,CElement &object)
  {
   int last_index=ResizeArray(m_wnd[window_index].m_timer_elements);
   m_wnd[window_index].m_timer_elements[last_index]=::GetPointer(object);
  }
//+------------------------------------------------------------------+
//| Hinzufügen der Pointer zum Ar. d. St. mit autom. Gr.-Änderung (X)|
//+------------------------------------------------------------------+
void CWndContainer::AddAutoXResizeElement(const int window_index,CElement &object)
  {
   int last_index=ResizeArray(m_wnd[window_index].m_auto_x_resize_elements);
   m_wnd[window_index].m_auto_x_resize_elements[last_index]=::GetPointer(object);
  }
//+------------------------------------------------------------------+
//| Hinzufügen der Pointer zum Ar. d. St. mit autom. Gr.-Änderung (Y)|
//+------------------------------------------------------------------+
void CWndContainer::AddAutoYResizeElement(const int window_index,CElement &object)
  {
   int last_index=ResizeArray(m_wnd[window_index].m_auto_y_resize_elements);
   m_wnd[window_index].m_auto_y_resize_elements[last_index]=::GetPointer(object);
  }
//+------------------------------------------------------------------+
//| Hinzufügen der Pointer zum Array der akt. verf. Steuerelemente   |
//+------------------------------------------------------------------+
void CWndContainer::AddAvailableElement(const int window_index,CElement &object)
  {
   int last_index=ResizeArray(m_wnd[window_index].m_available_elements);
   m_wnd[window_index].m_available_elements[last_index]=::GetPointer(object);
  }

Es gibt auch neue Methoden für den internen Gebrauch in der Klasse CWndEvents. Daher wird die Methode CWndEvents::Hide() für das Ausblenden aller Steuerelemente des grafischen Interfaces benötigt. Sie verwendet eine zweifache Schleife: die Erste, um die Formulare auszublenden, die Zweite, um die Steuerelemente des Formulars auszublenden. Bitte bedenken Sie, dass in dieser Methode die zweite Schleife über den Array der Steuerelemente iteriert, der aus Pointern auf die Hauptsteuerelemente besteht. Die Methoden Hide() und Show() der Steuerelemente sind nun so gestaltet, dass sie auf die gesamte Kette der Methoden der verschachtelten Steuerelemente in der ganzen Tiefe einwirken.

//+------------------------------------------------------------------+
//| Klasse der Ereignisbehandlung                                    |
//+------------------------------------------------------------------+
class CWndEvents : public CWndContainer
  {
protected:
   //--- Ausblenden aller Steuerelemente
   void              Hide();
  };
//+------------------------------------------------------------------+
//| Ausblenden der Steuerelemente                                    |
//+------------------------------------------------------------------+
void CWndEvents::Hide(void)
  {
   int windows_total=CWndContainer::WindowsTotal();
   for(int w=0; w<windows_total; w++)
     {
      m_windows[w].Hide();
      int main_total=MainElementsTotal(w);
      for(int e=0; e<main_total; e++)
        {
         CElement *el=m_wnd[w].m_main_elements[e];
         el.Hide();
        }
     }
  }

Ebenso gibt es die neue Methode CWndEvents::Show() zum Zeigen der Steuerelemente des jeweiligen Formulars. Das im Argument angegebene Fenster wird als erstes gezeigt. Dann, wenn das Fenster nicht minimiert ist, werden alle Steuerelemente dieses Formulars sichtbar gemacht. Die Schleife überspringt nur Steuerelemente, die (1) ausgewählt werden müssen (drop-down) oder (2) die mit einem Karteireiter, der als Hauptsteuerelement gekennzeichnet sind. Die Steuerelemente in Karteireiter werden später mit der Methode CWndEvents::ShowTabElements() gezeigt, außerhalb der Schleife.

class CWndEvents : public CWndContainer
  {
protected:
   //--- Zeigen der Steuerelemente des jeweiligen Fensters
   void              Show(const uint window_index);
  };
//+------------------------------------------------------------------+
//| Zeigen der Steuerelemente des jeweiligen Fensters                |
//+------------------------------------------------------------------+
void CWndEvents::Show(const uint window_index)
  {
//--- Zeigen der Steuerelemente des jeweiligen Fensters
   m_windows[window_index].Show();
//--- Wenn das Fenster nicht minimiert ist
   if(!m_windows[window_index].IsMinimized())
     {
      int main_total=MainElementsTotal(window_index);
      for(int e=0; e<main_total; e++)
        {
         CElement *el=m_wnd[window_index].m_main_elements[e];
         //--- Zeigen des Steuerelementes, wenn es (1) nicht ausgewählt werden muss (drop-down) und (2) und sein Hauptsteuerelement ist kein Karteireiter
         if(!el.IsDropdown() && dynamic_cast<CTabs*>(el.MainPointer())==NULL)
            el.Show();
        }
      //--- Zeigen nur der Steuerelemente des jeweiligen Karteireiter
      ShowTabElements(window_index);
     }
  }

Die Methode CWndEvents::Update() wird für das Neuzeichnen aller Steuerelemente des grafischen Interfaces der MQL-Anwendung benötigt. Diese Methode arbeitet in zwei Modi: (1) komplettes Neuzeichnen aller Steuerelemente oder (2) Umsetzen der vorher gemachten Änderungen. Um alles neuzuzeichnen und das grafische Interface zu aktualisieren, muss der Wert true übergeben werden.

class CWndEvents : public CWndContainer
  {
protected:
   //--- Neuzeichnen des Steuerelementes
   void              Update(const bool redraw=false);
  };
//+------------------------------------------------------------------+
//| Neuzeichnen des Steuerelementes                                  |
//+------------------------------------------------------------------+
void CWndEvents::Update(const bool redraw=false)
  {
   int windows_total=CWndContainer::WindowsTotal();
   for(int w=0; w<windows_total; w++)
     {
      //--- Neuzeichnen des Steuerelementes
      int elements_total=CWndContainer::ElementsTotal(w);
      for(int e=0; e<elements_total; e++)
        {
         CElement *el=m_wnd[w].m_elements[e];
         el.Update(redraw);
        }
     }
  }

Wir werden uns diesen Methoden etwas später zuwenden. Betrachten wir zunächst die zahlreichen Methoden für die Erstellung der Arrays der oben genannten Kategorien.

Die vorherige Version hatte eine zeitgesteuerte, allmähliche Änderung der Farbe eines Steuerelementes, wenn die Maus darüber schwebt. Um Umfang und Last zu reduzieren, wurde dieser überflüssige Schnickschnack entfernt. Der Timer wird daher in der aktuellen Version der Bibliothek nicht mehr in allen Steuerelementen benötigt. Er verbleibt nur für das schnelle Durchlaufen von (1) den Schiebereglern der Bildlaufleisten, (2) den Werten in den Spin-Bearbeitungsfeldern und (3) den Daten in dem Kalender. Daher werden nur diese Steuerelemente dem entsprechenden Array von der Methode CWndEvents::FormTimerElementsArray() hinzugefügt (siehe den Code unten). 

Da die Pointer auf die Steuerelemente in dem Array des Basistyps des Steuerelementes (CElement) gesichert werden, wird eine dynamische Typisierung (dynamic_cast) hier und in vielen anderen Methoden der Klassen verwendet, um den abgeleiteten Typ des Steuerelementes zu bestimmen. 

class CWndEvents : public CWndContainer
  {
protected:
   //--- Erstellen des Arrays der Steuerelemente mit einem Timer
   void              FormTimerElementsArray(void);
  };
//+------------------------------------------------------------------+
//| Erstellen des Arrays der Steuerelemente mit einem Timer          |
//+------------------------------------------------------------------+
void CWndEvents::FormTimerElementsArray(void)
  {
   int windows_total=CWndContainer::WindowsTotal();
   for(int w=0; w<windows_total; w++)
     {
      int elements_total=CWndContainer::ElementsTotal(w);
      for(int e=0; e<elements_total; e++)
        {
         CElement *el=m_wnd[w].m_elements[e];
         //---
         if(dynamic_cast<CCalendar    *>(el)!=NULL ||
            dynamic_cast<CColorPicker *>(el)!=NULL ||
            dynamic_cast<CListView    *>(el)!=NULL ||
            dynamic_cast<CTable       *>(el)!=NULL ||
            dynamic_cast<CTextBox     *>(el)!=NULL ||
            dynamic_cast<CTextEdit    *>(el)!=NULL ||
            dynamic_cast<CTreeView    *>(el)!=NULL)
           {
            CWndContainer::AddTimerElement(w,el);
           }
        }
     }
  }


Jetzt wird der Timer auch viel einfacher: Keine Notwendigkeit die geamte Liste der Steuerelemente zu prüfen, sondern nur die mit dieser Funktion:

//+------------------------------------------------------------------+
//| Prüfen der Ereignisse aller Steuerelemente durch den Timer       |
//+------------------------------------------------------------------+
void CWndEvents::CheckElementsEventsTimer(void)
  {
   int awi=m_active_window_index;
   int timer_elements_total=CWndContainer::TimerElementsTotal(awi);
   for(int e=0; e<timer_elements_total; e++)
     {
      CElement *el=m_wnd[awi].m_timer_elements[e];
      if(el.IsVisible())
         el.OnEventTimer();
     }
  }

Die Ereignisbehandlung von "Maus-über" wird auch nur für bestimmte Steuerelemente des grafischen Interfaces benötigt. Der Array der für die Bearbeitung solcher Ereignisse verfügbaren Steuerelemente schließt jetzt Folgendes aus: 

  • CButtonsGroup — Gruppen von Tasten;
  • CFileNavigator — Dateinavigator;
  • CLineGraph — Linienchart;
  • CPicture — Bilder;
  • CPicturesSlider — Schieberegler für Bilder;
  • CProgressBar — Fortschrittsanzeige;
  • CSeparateLine — Trennungslinie;
  • CStatusBar — Statuszeile;
  • CTabs — Karteireiter;
  • CTextLabel — Textkennzeichnung.

Alle diese Steuerelemente werden jetzt hervorgehoben, wenn sich die Maus darüber befindet. Einige von ihnen haben jedoch verschachtelte Steuerelemente, die hervorgehoben werden. Da aber der gemeinsame Array in einer Schleife verwendet wird, um den Array der verfügbaren Steuerelemente zu bilden, sind auch die verschachtelten Steuerelemente Teil dieser Auswahl. Der Array wählt alle Steuerelemente, die sichtbar, verfügbar und nicht gesperrt sind. 

class CWndEvents : public CWndContainer
  {
protected:
   //--- Erstellen des Arrays der verfügbaren Steuerelemente
   void              FormAvailableElementsArray(void);
  };
//+------------------------------------------------------------------+
//| Erstellen des Arrays der verfügbaren Steuerelemente              |
//+------------------------------------------------------------------+
void CWndEvents::FormAvailableElementsArray(void)
  {
//--- Fensterindex
   int awi=m_active_window_index;
//--- Gesamtzahl der Steuerelemente
   int elements_total=CWndContainer::ElementsTotal(awi);
//--- Löschen des Arrays
   ::ArrayFree(m_wnd[awi].m_available_elements);
//---
   for(int e=0; e<elements_total; e++)
     {
      CElement *el=m_wnd[awi].m_elements[e];
      //--- Ergänze nur die Steuerelemente, die sichtbar und verfügbar zur Bearbeitung sind
      if(!el.IsVisible() || !el.IsAvailable() || el.IsLocked())
         continue;
      //--- Ausschließen der Steuerelemente, die keine Ereignisbehandlung "Maus-über" benötigen
      if(dynamic_cast<CButtonsGroup   *>(el)==NULL &&
         dynamic_cast<CFileNavigator  *>(el)==NULL &&
         dynamic_cast<CLineGraph      *>(el)==NULL &&
         dynamic_cast<CPicture        *>(el)==NULL &&
         dynamic_cast<CPicturesSlider *>(el)==NULL &&
         dynamic_cast<CProgressBar    *>(el)==NULL &&
         dynamic_cast<CSeparateLine   *>(el)==NULL &&
         dynamic_cast<CStatusBar      *>(el)==NULL &&
         dynamic_cast<CTabs           *>(el)==NULL &&
         dynamic_cast<CTextLabel      *>(el)==NULL)
        {
         AddAvailableElement(awi,el);
        }
     }
  }

Kommen wir letztlich zu den Methoden CWndEvents::FormAutoXResizeElementsArray() und CWndEvents::FormAutoYResizeElementsArray(), die Arrays mit Pointern auf Steuerelemente erstellen, die über eine aktive automatische Größenänderung verfügen. Solche Steuerelemente passen sich an die Größe des zugehörigen Hauptsteuerelementes an. Nicht alle Steuerelemente haben eine Methode für eine automatische Größenänderung. Hier sind die, die das haben:

Steuerelemente mit dem Code der virtuellen Methode CElement::ChangeWidthByRightWindowSide() zur automatischen Breitenänderung:

  • CButton — Tasten.
  • CFileNavigator — Dateinavigator.
  • CLineGraph — Linienchart.
  • CListView — Listenansicht.
  • CMenuBar — Hauptmenü.
  • CProgressBar — Fortschrittsanzeige.
  • CStandardChart — Standardchart.
  • CStatusBar — Statuszeile.
  • CTable — Tabelle.
  • CTabs — Karteireiter.
  • CTextBox — Text-Bearbeitungsfeld.
  • CTextEdit — Eingabefeld.
  • CTreeView — Baumansicht.

Steuerelemente mit dem Code der virtuellen Methode CElement::ChangeHeightByBottomWindowSide() zur automatischen Höhenänderung:

  • CLineGraph — Linienchart.
  • CListView — Listenansicht.
  • CStandardChart — Standardchart.
  • CTable — Tabelle.
  • CTabs — Karteireiter.
  • CTextBox — Text-Bearbeitungsfeld.

Beim Erstellen der Arrays für diese Kategorien wird überprüft, ob der Modus zur automatischen Größenänderung in den Steuerelementen aktiv ist, dann werden sie dem Array hinzugefügt. Der Code wird weiter nicht aufgeführt: ähnlich Methoden wurden bereits beschrieben. 

Finden wir jetzt heraus, wann die Arrays der oben aufgelisteten Kategorien erstellt werden. In der Hauptmethode des Erstellens des grafischen Interfaces (die die Nutzer selbst baut) muss, nachdem alle benötigten Steuerelemente erfolgreich erstellt wurden, nur ein Methode CWndEvents::CompletedGUI() aufgerufen werden, um sie alle auf dem Chart darzustellen. Sie signalisiert auch dem Programm, dass das grafische Interface für die MQL-Anwendung fertig ist. 

Betrachten wir die Methode CWndEvents::CompletedGUI() im Detail. Sie ruft alle oben in diesem Kapitel beschriebenen Methoden auf. Zuerst sind alle Steuerelemente des grafischen Interfaces ausgeblendet. Keines von ihnen wurde bis jetzt gezeichnet. Damit wird ein fortlaufend sich änderndes Aussehen verhindert, sie werden vor dem Neuzeichnen ausgeblendet. Erst danach wird gezeichnet, so dass die Steuerelemente jeweils die letzten Änderungen erfahren haben. Dann müssen nur die Steuerelemente des Hauptfensters dargestellt werden. Danach wird der Array der Pointer auf die Steuerelemente je nach Kategorie erstellt. Am Ende der Methode wird der Chart aktualisiert. 

class CWndEvents : public CWndContainer
  {
protected:
   //--- Beenden der Erstellung des GUI
   void              CompletedGUI(void);
  };
//+------------------------------------------------------------------+
//| Beenden der Erstellung des GUI                                   |
//+------------------------------------------------------------------+
void CWndEvents::CompletedGUI(void)
  {
//--- Verlassen, wenn es noch kein Fenster gibt
   int windows_total=CWndContainer::WindowsTotal();
   if(windows_total<1)
      return;
//--- Zeigen des Kommentars zur Nutzerinformation
   ::Comment("Update. Please wait...");
//--- Ausblenden des Steuerelemente
   Hide();
//--- Zeichnen der Steuerelemente
   Update(true);
//--- Zeigen der Steuerelemente des aktiven Fensters
   Show(m_active_window_index);
//--- Erstellen des Arrays der Steuerelemente mit Timer
   FormTimerElementsArray();
//--- Erstellen des Arrays der sichtbaren und zeitgleich verfügbaren Steuerelemente
   FormAvailableElementsArray();
//--- Erstellen des Arrays der Steuerelemente mit automatischer Größenänderung
   FormAutoXResizeElementsArray();
   FormAutoYResizeElementsArray();
//--- Neuzeichnen des Charts
   m_chart.Redraw();
//--- Kommentar löschen
   ::Comment("");
  }

Die Methode CWndEvents::CheckElementsEvents() für die Prüfung und Bearbeitung von Ereignissen der Steuerelemente wurd entscheidend geändert. Schauen wir uns das im Detail an. 

Diese Methode hat jetzt zwei Blocks für die Ereignisbehandlung. Der ein Block bearbeitet exklusiv die Ereignisse der Mausbewegungen (CHARTEVENT_MOUSE_MOVE). Satt durch die Liste aller Steuerelemente des aktiven Fensters zu laufen, wie es vorher geschah, wird jetzt nur über die Steuerelemente iteriert, die für eine Bearbeitung verfügbar sind. Das ist der Grund, weshalb der Array mit den Pointern auf die verfügbaren Steuerelementen als ersten erzeugt worden war. Das grafische Interface einer umfangreichen MQL-Anwendung könnte über hunderte, ja vielleicht tausende Steuerelemente verfügen, aber und nur ein paar von denen sind in dem Moment sichtbar und verfügbar. Dieser Ansatz reduziert daher erheblich die CPU-Last. 

Geändert wurde auch, die Prüfungen, ob (1) das Unterfenster im Formular ist und (2) der Fokus auf dem Steuerelement liegt , sie werden jetzt in einer externen Schleife durchgeführt und nicht durch die Ereignisbehandlung von jeder Klasse der Steuerelemente. Ergo befinden sich jetzt die wichtigen Prüfungen der Steuerelemente am selben Ort. Dadurch wird es in der Zukunft leichter, falls es notwendig werden sollte, den Algorithmus der Ereignisbehandlung zu ändern.

Alle anderen Typen der Ereignisbehandlung befinden sich in eine eigenen Block. Die aktuelle Version iteriert über die ganze Liste der Steuerelemente des grafischen Interfaces. Alle Prüfungen, die vorher in den Klassen der Steuerelemente lagen, wurden nun in eine externe Schleife verschoben. 

Ganz am Ende der Methode wird das Ereignis an die Nutzerklasse der MQL-Anwendung gesendet.

//+------------------------------------------------------------------+
//| Prüfen der Ereignisse des Steuerelementes                        |
//+------------------------------------------------------------------+
void CWndEvents::CheckElementsEvents(void)
  {
//--- Ereignisbehandlung des Mauskursors
   if(m_id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Verlassen, wenn das Formular in einem anderen Unterfenster des Charts liegt
      if(!m_windows[m_active_window_index].CheckSubwindowNumber())
         return;
      //--- Prüfen nur der verfügbaren Steuerelemente
      int available_elements_total=CWndContainer::AvailableElementsTotal(m_active_window_index);
      for(int e=0; e<available_elements_total; e++)
        {
         CElement *el=m_wnd[m_active_window_index].m_available_elements[e];
         //--- Prüfen des Fokus auf den Steuerelementen
         el.CheckMouseFocus();
         //--- Ereignisbehandlung
         el.OnEvent(m_id,m_lparam,m_dparam,m_sparam);
        }
     }
//--- Alle Ereignisse, außer der Mausbewegungen
   else
     {
      int elements_total=CWndContainer::ElementsTotal(m_active_window_index);
      for(int e=0; e<elements_total; e++)
        {
         //--- Prüfen nur der verfügbaren Steuerelemente
         CElement *el=m_wnd[m_active_window_index].m_elements[e];
         if(!el.IsVisible() || !el.IsAvailable() || el.IsLocked())
            continue;
         //--- Ereignisbehandlung durch die Ereignisbehandlung des Steuerelementes
         el.OnEvent(m_id,m_lparam,m_dparam,m_sparam);
        }
     }
//--- Weiterleiten des Ereignisses an die Anwendungsdatei
   OnEvent(m_id,m_lparam,m_dparam,m_sparam);
  }

Die Methode CWndEvents::FormAvailableElementsArray() zum Erstellen des Arrays der sichtbaren und aktuell zur Bearbeitung verfügbaren Steuerelemente wird in folgenden Fällen aufrufen:

  • Öffnen eines Dialogfeldes. Wenn ein Dialogfeld geöffnet wird, wird das Ereignis ON_OPEN_DIALOG_BOX erzeugt, das von der Methode CWndEvents::OnOpenDialogBox() bearbeitet wird. Nach der Bearbeitung diese Ereignisses muss ein Array der verfügbaren Steuerelemente des geöffneten Fensters erstellt werden.
  • Änderungen im grafischen Interface. Jede Änderung des grafischen Interfaces verursacht das Ereignis ON_CHANGE_GUI. Das wird bearbeitet durch die neue private Methode CWndEvents::OnChangeGUI(). Hier, sobald die Nachricht ON_CHANGE_GUI ankommt, wird zuerst der Array der verfügbaren Steuerelemente erzeugt. Dann werden alle Tooltips auf die oberste Ebene verschoben. Am Ende der Methode wird der Chart neu gezeichnet, um die letzten Änderungen anzuzeigen.
class CWndEvents : public CWndContainer
  {
private:
   //--- Änderungen des grafischen Interfaces
   bool              OnChangeGUI(void);
  };
//+------------------------------------------------------------------+
//| Ereignis der Änderung im grafischen Interface                    |
//+------------------------------------------------------------------+
bool CWndEvents::OnChangeGUI(void)
  {
//--- Wenn eine Änderung des grafischen Interfaces signalisiert wurde
   if(m_id!=CHARTEVENT_CUSTOM+ON_CHANGE_GUI)
      return(false);
//--- Erstellen des Arrays der sichtbaren und zeitgleich verfügbaren Steuerelemente
   FormAvailableElementsArray();
//--- Move tooltips to the top layer
   ResetTooltips();
//--- Neuzeichnen des Charts
   m_chart.Redraw();
   return(true);
  }

Schauen wir uns jetzt an, wie ein Ereignis mit dem Identifikator ON_SET_AVAILABLE behandelt wird, um jene Steuerelemente zu identifizieren, die für eine Bearbeitung verfügbar sind. 

Die Methode CWndEvents::OnSetAvailable() wurde für die Bearbeitung des Ereignisses ON_SET_AVAILABLE implementiert. Aber vor der Beschreibung deren Codes, müssen ein paar Hilfsmethoden besprochen werden. Es gibt 10 Steuerelemente des grafischen Interfaces, die Ereignisse mit diesen Identifikatoren erstellen. Die können alle ihren aktiven Zustand bestimmen. Benennen wir sie:

  • Hauptmenü — CMenuBar::State().
  • Menüelement — CMenuItem::GetContextMenuPointer().IsVisible().
  • Splitt-Taste — CSplitButton::GetContextMenuPointer().IsVisible().
  • Kombinationsfeld — CComboBox::GetListViewPointer().IsVisible().
  • Auswahlkalender — DropCalendar::GetCalendarPointer().IsVisible().
  • Bildlaufleiste — CScroll::State().
  • Tabelle — CTable::ColumnResizeControl().
  • Numerische Schieberegler — CSlider::State().
  • Baumansicht — CTreeView::GetMousePointer().State().
  • Standardchart — CStandartChart::GetMousePointer().IsVisible().

Jede dieser Steuerelemente hat einen privaten Array in der Klasse CWndContainer. Die Klasse CWndEvents implementiert Methoden, um zu bestimmen, welche der Steuerelemente ist aktuell aktiv. Alle diese Methoden geben den Index des aktiven Steuerelementes im eigenen privaten Array zurück.

class CWndEvents : public CWndContainer
  {
private:
   //--- Rückgabe des Index des aktiven Hauptmenüs
   int               ActivatedMenuBarIndex(void);
   //--- Rückgabe des Index des aktiven Menüelements
   int               ActivatedMenuItemIndex(void);
   //--- Rückgabe des Index der aktiven Splitt-Taste
   int               ActivatedSplitButtonIndex(void);
   //--- Rückgabe des Index des aktiven Kombinationsfeldes
   int               ActivatedComboBoxIndex(void);
   //--- Rückgabe des Index des aktiven Kalenders
   int               ActivatedDropCalendarIndex(void);
   //--- Rückgabe des Index der aktiven Bildlaufleiste
   int               ActivatedScrollIndex(void);
   //--- Rückgabe des Index der aktiven Tabelle
   int               ActivatedTableIndex(void);
   //--- Rückgabe des Index des aktiven Schiebereglers
   int               ActivatedSliderIndex(void);
   //--- Rückgabe des Index der aktiven Baumansicht
   int               ActivatedTreeViewIndex(void);
   //--- Rückgabe des Index des aktiven Subcharts
   int               ActivatedSubChartIndex(void);
  };

Da der einzige Unterschied zwischen den meisten dieser Methoden nur in den Bedingungen liegt, die den Zustand des Steuerelementes bestimmen, besprechen wir nur den Code von einer von ihnen. Unten ist der Code der Methode CWndEvents::ActivatedTreeViewIndex() aufgeführt, die den Index der aktiven Baumansicht zurückgibt. Wenn für diesen Typ eines Steuerelementes der Modus für Karteireiterelemente aktiviert ist, wird die Prüfung zurückgewiesen.

//+------------------------------------------------------------------+
//| Rückgabe des Index der aktiven Baumansicht                       |
//+------------------------------------------------------------------+
int CWndEvents::ActivatedTreeViewIndex(void)
  {
   int index=WRONG_VALUE;
//---
   int total=ElementsTotal(m_active_window_index,E_TREE_VIEW);
   for(int i=0; i<total; i++)
     {
      CTreeView *el=m_wnd[m_active_window_index].m_treeview_lists[i];
      //--- Gehe zum Nächsten, wenn der Tab-Modus aktiv ist
      if(el.TabItemsMode())
         continue;
      //--- Falls gerade die Breite der Listen geändert wird 
      if(el.GetMousePointer().State())
        {
         index=i;
         break;
        }
     }
   return(index);
  }

Die Methode CWndEvents::SetAvailable() soll den Zustand der Verfügbarkeit des Steuerelementes festlegen. Als Argumente müssen übergeben werden: (1) der benötigte Index und (2) der festzulegende Zustand des Steuerelementes. 

Falls alle Steuerelemente nicht-verfügbar gemacht werden sollen, muss man einfach in einer Schleife bei allen diesen Wert auf false setzen. 

Wenn die Steuerelemente verfügbar gemacht werden müssen, dann wird die überladene Methode CTreeView::IsAvailable() desselben Namens für die Baumansicht aufgerufen, die zwei Arten hat, den Zustand zu setzen: (1) nur für das Hauptsteuerelement und (2) für alle Steuerelemente in der ganzen Tiefe der Verschachtelung. Daher wird hier die dynamische Typisierung verwendet, um den Pointer auf das Steuerelement der abgeleiteten Klasse des Steuerelementes abzufragen

class CWndEvents : public CWndContainer
  {
protected:
   //--- Setzen des Zustandes des Steuerelementes auf verfügbar
   void              SetAvailable(const uint window_index,const bool state);
  };
//+------------------------------------------------------------------+
//| Setzen des Zustandes des Steuerelementes auf verfügbar           |
//+------------------------------------------------------------------+
void CWndEvents::SetAvailable(const uint window_index,const bool state)
  {
//--- Abfrage der Nummer des Hauptsteuerelementes
   int main_total=MainElementsTotal(window_index);
//--- Falls nötig wird das Steuerelement nicht-verfügbar
   if(!state)
     {
      m_windows[window_index].IsAvailable(state);
      for(int e=0; e<main_total; e++)
        {
         CElement *el=m_wnd[window_index].m_main_elements[e];
         el.IsAvailable(state);
        }
     }
   else
     {
      m_windows[window_index].IsAvailable(state);
      for(int e=0; e<main_total; e++)
        {
         CElement *el=m_wnd[window_index].m_main_elements[e];
         //--- Falls Baumansicht
         if(dynamic_cast<CTreeView*>(el)!=NULL)
           {
            CTreeView *tv=dynamic_cast<CTreeView*>(el);
            tv.IsAvailable(true);
            continue;
           }
         //--- Falls Dateinavigator
         if(dynamic_cast<CFileNavigator*>(el)!=NULL)
           {
            CFileNavigator *fn =dynamic_cast<CFileNavigator*>(el);
            CTreeView      *tv =fn.GetTreeViewPointer();
            fn.IsAvailable(state);
            tv.IsAvailable(state);
            continue;
           }
         //--- Steuerelemente verfügbar machen
         el.IsAvailable(state);
        }
     }
  }

Elemente eines Menüs, die ihrerseits Kontextmenü haben, benötigen eine Methode, die in einer Schleife in der gesamten Tiefe der geöffneten Kontextmenüs auf alle Elemente zugreifen kann. In diesem Fall ist es notwendig, die Kontextmenüs verfügbar für eine Bearbeitung zu machen. Das wird rekursiv verwirklicht. 

Unten ist der Code der Methode CWndEvents::CheckContextMenu(). Zuerst wird ein Objekt des Typs des Menüelements übergeben, und es wird versucht, den Pointer auf das Kontextmenü abzufragen. Ist der Pointer korrekt, wird überprüft, ob das Kontextmenü geöffnet ist. Wenn ja, wird der Zustand auf verfügbar gesetzt, und dann, in einer Schleife, auch alle Elemente dieses Menüs. Zugleich wird auch bei jedem Element mit der Methode CWndEvents::CheckContextMenu() überprüft, ob es ein Kontextmenü hat.

class CWndEvents : public CWndContainer
  {
private:
   //--- Prüfen und verfügbar machen des Kontextmenüs
   void              CheckContextMenu(CMenuItem &object);
  };
//+------------------------------------------------------------------+
//| Rekursiv prüfen und verfügbar machen des Kontextmenüs            |
//+------------------------------------------------------------------+
void CWndEvents::CheckContextMenu(CMenuItem &object)
  {
//--- Abfrage des Pointers auf das Kontextmenü
   CContextMenu *cm=object.GetContextMenuPointer();
//--- Verlassen, wenn es kein Kontextmenü als Element gibt
   if(::CheckPointer(cm)==POINTER_INVALID)
      return;
//--- Verlassen, wenn es ein Kontextmenü gibt, aber es ist ausgeblendet
   if(!cm.IsVisible())
      return;
//--- Setzen der Verfügbarkeit des Steuerelementes
   cm.IsAvailable(true);
//---
   int items_total=cm.ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Setzen der Verfügbarkeit des Steuerelementes
      CMenuItem *mi=cm.GetItemPointer(i);
      mi.IsAvailable(true);
      //--- Prüfen, ob dieses Element ein Kontextmenü hat
      CheckContextMenu(mi);
     }
  }

Betrachten wir jetzt die Methode CWndEvents::OnSetAvailable(), die auf Grund der Ereignisbehandlung die Verfügbarkeit des Steuerelementes bestimmt. 

Wenn vom Nutzer ein Ereignis mit dem Identifikator ON_SET_AVAILABLE empfangen wurde, muss als erstes festgestellt werden, ob es ein aktuell aktives Steuerelement gibt. Die lokalen Variablen sichern die Indizes der aktivierten Steuerelemente für einen beschleunigten Zugriff auf deren privaten Arrays.

Wenn ein Signal zur Bestimmung der verfügbaren Steuerelemente empfangen wurde, dann wird als erstes der Zugriff auf die gesamte Liste gesperrt. Ist es das Signal, alles wiederherzustellen, dann wird, nach der Prüfung der Abwesenheit von aktiven Steuerelementen, der Zugriff in der gesamten Liste wiederhergestellt und die Methode wird verlassen.

Wenn das Programm den nächsten Codeblock dieser Methode erreicht, das heißt es gibt ein Signal entweder (1) die verfügbaren Steuerelemente zu bestimmen oder (2) wiederherzustellen, aber es gibt einen aktiven Kalender. Die zweite Situation kann auftreten, wenn eine Kalender mit aktivem Kombinationsfeld geöffnet wird, in dem eine Auswahlliste geschlossen wurde.

Wenn einer der beschriebenen Bedingungen zutrifft, wird versucht, sich den Pointer auf das aktivierte Steuerelemente zu holen. Wenn der Pointer nicht geholt werden konnte, wird die Methode verlassen.

Wurde ein Pointer erhalten, wird das Steuerelement verfügbar gemacht. Für einige Steuerelemente gibt es kleine Unterschiede, wie das durchgeführt wird. Hier sind alle diese Klassen:

  • Hauptmenü (CMenuBar). Wenn es aktiviert wurde, müssen alle verbundenen, geöffneten Kontextmenüs verfügbar gemacht werden. Zu diesem Zweck wurde bereits der Code der rekursiven Methode CWndEvents::CheckContextMenu() besprochen.
  • Menüelement (CMenuItem). Menüelemente können unabhängige Steuerelemente sein, denen ein Kontextmenü zugeordnet ist. Daher, falls solch ein Steuerelemente aktiviert ist, wird auch das Steuerelement selbst (Menüelement) verfügbar gemacht.
  • Bildlaufleiste (CScroll). Wenn eine Bildlaufleiste aktiviert ist (Schieberegler in Bewegung), dann werden all Steuerelemente, beginnend beim ersten, aktiviert. Wenn zum Beispiel die Bildlaufleiste einer Listenansicht zugeordnet ist, dann werden die Listenansicht und alle ihre Steuerelemente verfügbar, in der ganzen Tiefe der Verschachtelung.
  • Baumansicht (CTreeView). Dieses Steuerelement kann aktiviert werden, wenn die Breite seiner Liste geändert wurde. Die Bearbeitung der Listenansicht muss beim "Maus-über" verhindert werden und die Baumansicht selbst muss für eine Bearbeitung verfügbar gemacht werden.

Unten ist der Code der Methode CWndEvents::OnSetAvailable().

class CWndEvents : public CWndContainer
  {
private:
   //--- Bestimmen der verfügbaren Steuerelemente
   bool              OnSetAvailable(void);
  };
//+------------------------------------------------------------------+
//| Ereignis zum Bestimmen der verfügbaren Steuerelemente            |
//+------------------------------------------------------------------+
bool CWndEvents::OnSetAvailable(void)
  {
//--- Wenn die Änderung der Verfügbarkeit des Steuerelementes signalisiert wurde
   if(m_id!=CHARTEVENT_CUSTOM+ON_SET_AVAILABLE)
      return(false);
//--- Signal zu setzen/wiederherzustellen
   bool is_restore=(bool)m_dparam;
//--- Determine the active controls
   int mb_index =ActivatedMenuBarIndex();
   int mi_index =ActivatedMenuItemIndex();
   int sb_index =ActivatedSplitButtonIndex();
   int cb_index =ActivatedComboBoxIndex();
   int dc_index =ActivatedDropCalendarIndex();
   int sc_index =ActivatedScrollIndex();
   int tl_index =ActivatedTableIndex();
   int sd_index =ActivatedSliderIndex();
   int tv_index =ActivatedTreeViewIndex();
   int ch_index =ActivatedSubChartIndex();
//--- Wenn die Bestimmung der Verfügbarkeit signalisiert wurde, zuerst Zugriff sperren 
   if(!is_restore)
      SetAvailable(m_active_window_index,false);
//--- Wiederherstellen nur, wenn es keine aktivierte Elemente gibt
   else
     {
      if(mb_index==WRONG_VALUE && mi_index==WRONG_VALUE && sb_index==WRONG_VALUE &&
         dc_index==WRONG_VALUE && cb_index==WRONG_VALUE && sc_index==WRONG_VALUE &&
         tl_index==WRONG_VALUE && sd_index==WRONG_VALUE && tv_index==WRONG_VALUE && ch_index==WRONG_VALUE)
        {
         SetAvailable(m_active_window_index,true);
         return(true);
        }
     }
//--- Wenn signalisiert wurde (1) der Zugriff zu sperren oder (2) den Kalender wiederherzustellen
   if(!is_restore || (is_restore && dc_index!=WRONG_VALUE))
     {
      CElement *el=NULL;
      //--- Hauptmenü
      if(mb_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_menu_bars[mb_index];      }
      //--- Menüelement
      else if(mi_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_menu_items[mi_index];     }
      //--- Splitt-Taste
      else if(sb_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_split_buttons[sb_index];  }
      //--- Kalender ohne Auswahlliste
      else if(dc_index!=WRONG_VALUE && cb_index==WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_drop_calendars[dc_index]; }
      //--- Auswahlliste
      else if(cb_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_combo_boxes[cb_index];    }
      //--- Bildlaufleiste
      else if(sc_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_scrolls[sc_index];        }
      //--- Tabelle
      else if(tl_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_tables[tl_index];         }
      //--- Schieberegler
      else if(sd_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_sliders[sd_index];        }
      //--- Baumansicht
      else if(tv_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_treeview_lists[tv_index]; }
      //--- Subchart
      else if(ch_index!=WRONG_VALUE)
        { el=m_wnd[m_active_window_index].m_sub_charts[ch_index];     }
      //--- Verlassen, wenn kein Pointer erhalten wurde
      if(::CheckPointer(el)==POINTER_INVALID)
         return(true);
      //--- Block des Hauptmenüs
      if(mb_index!=WRONG_VALUE)
        {
         //--- Verfügbar machen des Hauptmenüs und seine sichtbaren Kontextmenüs
         el.IsAvailable(true);
         //--- 
         CMenuBar *mb=dynamic_cast<CMenuBar*>(el);
         int items_total=mb.ItemsTotal();
         for(int i=0; i<items_total; i++)
           {
            CMenuItem *mi=mb.GetItemPointer(i);
            mi.IsAvailable(true);
            //--- Prüfen und verfügbar machen des Kontextmenüs
            CheckContextMenu(mi);
           }
        }
      //--- Block für die Menüelemente
      if(mi_index!=WRONG_VALUE)
        {
         CMenuItem *mi=dynamic_cast<CMenuItem*>(el);
         mi.IsAvailable(true);
         //--- Prüfen und verfügbar machen des Kontextmenüs
         CheckContextMenu(mi);
        }
      //--- Block für die Bildlaufleiste
      else if(sc_index!=WRONG_VALUE)
        {
         //--- Make available starting from the main node
         el.MainPointer().IsAvailable(true);
        }
      //--- Block für die Baumansicht
      else if(tv_index!=WRONG_VALUE)
        {
         //--- Sperren aller Steuerelemente außer dem Hauptsteuerelement
         CTreeView *tv=dynamic_cast<CTreeView*>(el);
         tv.IsAvailable(true,true);
         int total=tv.ElementsTotal();
         for(int i=0; i<total; i++)
            tv.Element(i).IsAvailable(false);
        }
      else
        {
         //--- Steuerelemente verfügbar machen
         el.IsAvailable(true);
        }
     }
//---
   return(true);
  }



Anwendung zum Testen der Kontrollelemente

Eine MQL-Anwendung wurde zum Zwecke des Testens mitgegeben. Dessen grafisches Interface umfasst alle Steuerelemente der Bibliothek. Und so schaut alles aus: 

Fig. 12. Grafisches Interface zum Testen in einer MQL-Anwendung.

Fig. 12. Grafisches Interface zum Testen in einer MQL-Anwendung.


Sie können sie am Ende des Artikels für weitere Untersuchungen herunterladen.



Schlussfolgerung

Diese Version der Bibliothek weist erhebliche Unterschiede auf zu der Version, die in dem Artikel Graphisches Interface X: Textauswahl im mehrzeiligen Textfeld (build 13) beschrieben ist. Es wurde viel Arbeit investiert, die fast alle Dateien der Bibliothek betrifft. Jetzt werden alle Steuerelemente der Bibliothek als eigenständige Objekte gezeichnet. Die Lesbarkeit des Codes wurde verbessert, der Codeumfang um etwa 30% verringert und die Fähigkeiten wurden erweitert. Einige Fehler und Mängel, die von den Nutzern bekannt gemacht wurden, wurden behoben.

Wenn Sie gerade begonnen haben eine MQL-Anwendung zu erstellen und Sie die vorherige Version der Bibliothek verwenden, ist es empfehlenswert, zunächst die neue Version als eine eigenständige Kopie herunterzuladen und für das MetaTrader 5-Terminal zu installieren, um alles ausgiebig testen zu können.

Das Schema der Bibliothek des grafischen Interfaces im aktuellen Entwicklungsstand schaut aus, wie unten abgebildet. Das ist nicht die letzte Version der Bibliothek: sie wird sich in der Zukunft weiterentwickeln und verbessern.

 Fig. 13. Die Struktur der Bibliothek im aktuellen Zustand der Entwicklung

Fig. 13. Die Struktur der Bibliothek im aktuellen Zustand der Entwicklung



Bei Fragen zur Verwendung des bereitgestellten Materials, können Sie auf die detaillierten Beschreibungen im Lauf der Entwicklung der Bibliothek in den vorangegangenen Artikeln dieser Serie zurückgreifen oder Sie stellen Ihre Fragen im Kommentarteil diese Artikels.