Graphisches Interface XI: Gezeichnete Steuerelemente (build 14.2)
Inhalt
- Einführung
- Methoden zum Zeichnen der Steuerelemente
- Das neue Design des grafischen Interfaces
- Tooltips
- Die Identifikatoren für neue Ereignisse
- Optimierung des Kerns der Bibliothek
- Anwendung zum Testen der Kontrollelemente
- Schlussfolgerung
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.
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.
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.
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).
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.
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
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.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/3366
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.