
Grafische Interfaces II: Einrichtung des Eventhandlers für die Bibliothek (Kapitel 3)
Inhalt
- Einleitung
- Privates Array der Elemente
- Verwalten des Status des Charts
- Bezeichner für die externe und interne Verwendung
- Erweitern der Kontextmenü Klasse
- Erweitern der Menüpunkt Klasse
- Erweitern der Hauptklasse für die Verwaltung der Events des grafischen Interfaces
- Ein vorläufiger Test der Eventhandler
- Test von verschiedenen Kontextmenüs und eine feinere Abstimmung
- Test für das Empfangen von Nachrichten in der benutzerdefinierten Klasse der Anwendung
- Schlussfolgerung
Einleitung
Der erste Artikel Grafisches Interface I: Vorbereiten der Bibliotheksstruktur (Kapitel 1) beschreibt im Detail wofür diese Bibliothek gedacht ist. Eine vollständige Liste mit Links zu diesem Artikel des ersten Teils, finden Sie am Ende von jedem Kapitel. Zudem finden Sie dort eine Möglichkeit das Projekt, entsprechend dem aktuellen Entwicklungsstand, herunterzuladen. Die Dateien müssen in den gleichen Verzeichnissen untergebracht werden, so, wie Sie auch in dem Archiv abgelegt sind.
Der vorherige Artikel beinhaltet die Implementation der Klassen für das Erzeugen der Bestandteile des Hauptmenüs. Die Entwicklungen der Klassen für jedes Control benötigen noch eine Feinjustage der Eventhandler in den Hauptklassen und in den Klassen der erzeugten Controls In diesem Artikel behandeln wir noch die folgende Frage:
- Private Arrays für jedes sinnvolle Control.
- Das Hinzufügen der Element-Pointer zu der Basis. Diese Elemente sind Bestandteile komplexer (verbundener) Elemente.
- Verwalten des Status des Charts in Abhängigkeit der Position des Mauszeigers.
- Die Bezeichner der Bibliothek-Events für den internen und externen Gebrauch.
Zudem zeigen wir noch den Prozess für das Erhalten und Verarbeiten von Event-Nachrichten in der benutzerdefinierten Klasse eine Anwendung.
Privates Array der Elemente
Lassen Sie uns ein kleines Experiment durchführen. Klicken Sie mit der linken Maustaste auf ein Kontextmenüelement in dem Bereich, wo sich der Mauszeiger außerhalb des Bereiches des Formulars befindet. Wir stellen hier fest, dass das Scrollen des Charts nicht deaktiviert wurde und es genutzt werden kann, während sich der Mauszeiger über dem Control befindet. Dieses ist ein funktionaler Fehler und er sollte nicht existieren. Wir werden es nun so einrichten, dass, egal über welchem Controls sich der Mauszeiger befindet, dass scrollen des Charts und das Verschieben von Trading levels in diesem Moment deaktiviert ist.
Zunächst einmal fügen wir die Nachverfolgung des Fokus eines Elementes dem Kontextmenü-Handler hinzu, so wie es in dem nachfolgenden Programmcode gezeigt wird. Falls das Kontext-Menü versteckt ist, dann brauchen wir nichts weiter zu unternehmen. Folgen Sie immer diesem Ansatz um Zeit zu sparen.
//+-----------------------------------------------------------------+ //| Event handler | //+-----------------------------------------------------------------+ void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { if(id==CHARTEVENT_MOUSE_MOVE) { //--- Abbrechen, falls das Element versteckt ist if(!CElement::m_is_visible) return; //--- Erhalten des Focus int x=(int)lparam; int y=(int)dparam; CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); } }
Bei dem augenblicklichen Stand der Entwicklung, enthält in die CWndContainer Klasse das gemeinsame Array m_elements[] mit den Element-Pointern. Dieses ist ein Teil der WindowElements-Struktur des Element-Arrays. Dieses Array ist für alle Fälle geeignet, wo eine Aktion auf Bedienelemente oder zumindest auf eine Mehrheit von ihnen, angewandt werden muss. Falls aber eine Aktion nur einer bestimmten Gruppe von Elementen zugewiesen werden soll, dann ist dieser Ansatz übertrieben und er benötigt viel zu viele Ressourcen. Lassen Sie uns zum Beispiel eine Gruppe von Controls betrachten, deren Größe die Grenzen des Formulars überschreiten kann. Drop-down Listen und Kontextmenüs gehören zu dieser Gruppe. Jeder Typ dieser Elemente muss in einem separaten Array abgespeichert werden. Dieses erlaubt uns eine viel effizientere und einfachere Verwaltung.
Fügen Sie das Array für die Kontextmenüs der WindowElements Struktur hinzu und erzeugen Sie eine Methode für die Abfrage der Größe:
//+-----------------------------------------------------------------+ //| Klasse für das Abspeichern aller Interface Objekte | //+-----------------------------------------------------------------+ class CWndContainer { protected: //--- Struktur des Elementen-Arrays struct WindowElements { //--- Gemeinsames Array von allen Objekten CChartObject *m_objects[]; //--- Gemeinsames Array aller Elemente CElement *m_elements[]; //--- Ein privates Array mit Elementen: // Kontextmenü Array CContextMenu *m_context_menus[]; }; //--- Ein Array mit Element Arrays für jedes Fenster WindowElements m_wnd[]; //--- public: //--- Anzahl der Kontextmenüs int ContextMenusTotal(const int window_index); //--- }; //+----------------------------------------------------------------------------+ //| Gibt die Anzahl der Kontextmenüs über den angegebenen Fenster-Index zurück | //+----------------------------------------------------------------------------+ int CWndContainer::ContextMenusTotal(const int window_index) { if(window_index>=::ArraySize(m_wnd)) { ::Print(PREVENTING_OUT_OF_RANGE); return(WRONG_VALUE); } //--- return(::ArraySize(m_wnd[window_index].m_context_menus)); }
Jedesmal, nachdem ein Control in der benutzerdefinierten Klasse der Anwendung (in unserem Fall CProgram) erzeugt wurde, verwenden wir die CWndContainer::AddToElementsArray() Methode um einen Pointer dieses Controls der Basis hinzuzufügen. Innerhalb in dieser Methode, werden Methoden für die Abfrage und das Abspeichern von Pointern zu jedem komplexen (verbundenen) Element in dem gemeinsamen Array verwendet. Eine ähnliche Methode CWndContainer::AddContextMenuElements() wurde zuvor schon für das Kontext-Menü erzeugt. Alle ähnlichen Methoden bieten eine Möglichkeit Pointer in den privaten Arrays der Elemente zu verteilen, falls dieses notwendig ist.
Anschließend benötigen wir eine Methoden-Vorlage für das Hinzufügen eines über einen Link übergebenen Element-Pointers zu dem Array, da diese Aktion mehr als nur einmal bei den verschiebenen Objekttypen wiederholt werden muss.
class CWndContainer { protected: //--- Methoden-Vorlage für das Hinzufügen eines Pointers zu dem Array, übergeben über einen Link template<typename T1,typename T2> void AddToRefArray(T1 &object,T2 &ref_array[]); //--- }; //+-------------------------------------------------------------------------+ //| Speichert den Pointer (T1) In dem Array, übergeben durch einen Link(T2) | //+-------------------------------------------------------------------------+ template<typename T1,typename T2> void CWndContainer::AddToRefArray(T1 &object,T2 &array[]) { int size=::ArraySize(array); ::ArrayResize(array,size+1); array[size]=object; }
Jetzt kann der Pointer des Kontextmenüs am Ende der CWndContainer::AddContextMenuElements() Methode in einem privaten Array abgespeichert werden, wie nachfolgend gezeigt: (Hervorgehoben in Gelb). Lassen Sie uns dieses auch für alle anderen Controls durchführen.
//+-----------------------------------------------------------------+ //| Speichert den Pointer zu den Kontextmenü-Objekten in der Basis | //+-----------------------------------------------------------------+ bool CWndContainer::AddContextMenuElements(const int window_index,CElement &object) { //--- Verlassen, falls es sich nicht um ein Kontext-Menü handelt if(object.ClassName()!="CContextMenu") return(false); //--- Abfragen des Pointers des Kontextmenüs CContextMenu *cm=::GetPointer(object); //--- Abspeichern der Pointer zu den Objekten in der Basis int items_total=cm.ItemsTotal(); for(int i=0; i<items_total; i++) { //--- Vergrößern des Elementen Arrays int size=::ArraySize(m_wnd[window_index].m_elements); ::ArrayResize(m_wnd[window_index].m_elements,size+1); //--- Erhalt des menu-item pointers CMenuItem *mi=cm.ItemPointerByIndex(i); //--- Abspeichern des pointers in dem Array m_wnd[window_index].m_elements[size]=mi; //--- Hinzufügen aller Pointer von allen Objekten eines Menüelementes zu dem gemeinsamen Array AddToObjectsArray(window_index,mi); } //--- Hinzufügen des Pointers zu dem privaten Array AddToRefArray(cm,m_wnd[window_index].m_context_menus); return(true); }
Verwalten des Status des Charts
Anschließend muss in der CWndEvents Klasse eine Methode für die Überprüfung des Fokus des Mauszeigers über den Controls hinzugefügt werden. Eine solche Überprüfung wird für Formulare und Dropdownlisten durchgeführt. Die Formulare und die Kontextmenüs besitzen bereits private Arrays. Lassen Sie uns dafür die CWndEvents::SetChartState() Methode erzeugen. Nachfolgend finden Sie die Deklaration und Implementation dieser Methode:
class CWndEvents : public CWndContainer { private: //--- Festlegen des Chart-Status void SetChartState(void); }; //+-----------------------------------------------------------------+ //| Legt den Status des Chart fest | //+-----------------------------------------------------------------+ void CWndEvents::SetChartState(void) { //--- Für die Identifizierung des Events, wenn die Verwaltung deaktiviert werden muss bool condition=false; //--- Überprüfen der Fenster int windows_total=CWndContainer::WindowsTotal(); for(int i=0; i<windows_total; i++) { //--- Mit dem nächsten Formular fortfahren, falls diese versteckt ist if(!m_windows[i].IsVisible()) continue; //--- Überprüfen der Bedingungen in dem internen Handler des Formulars m_windows[i].OnEvent(m_id,m_lparam,m_dparam,m_sparam); //--- Registriere den Fokus, falls dieser existiert if(m_windows[i].MouseFocus()) { condition=true; break; } } //--- Überprüfe den Fokus des Kontextmenüs if(!condition) { int context_menus_total=CWndContainer::ContextMenusTotal(0); for(int i=0; i<context_menus_total; i++) { if(m_wnd[0].m_context_menus[i].MouseFocus()) { condition=true; break; } } } //--- if(condition) { //--- Die Aktivierung des Scrollens und das Management der Handelsebenen m_chart.MouseScroll(false); m_chart.SetInteger(CHART_DRAG_TRADE_LEVELS,false); } else { //--- Aktiviere die Verwaltung m_chart.MouseScroll(true); m_chart.SetInteger(CHART_DRAG_TRADE_LEVELS,true); } }
Für die augenblickliche Aufgabenstellung ist diese Methode ausreichend, aber wir werden Sie später noch ein wenig erweitern. Sie muss in der CWndEvents::ChartEventMouseMove() Methode aufgerufen werden, wie nachfolgend gezeigt wird:
//+----------------------------------------------------------------+ //| CHARTEVENT MOUSE MOVE event | //+----------------------------------------------------------------+ void CWndEvents::ChartEventMouseMove(void) { //--- Abbrechen, falls es sich nicht um eine Verschiebung des Mauszeigers handelt if(m_id!=CHARTEVENT_MOUSE_MOVE) return; //--- Verschieben des Fensters MovingWindow(); //--- Festlegen des Chart-Status SetChartState(); //--- Neuzeichnen des Charts m_chart.Redraw(); }
Kompilieren Sie alle Dateien und testen Sie den EA. Wir sehen nun, dass wenn man mit der linken Maustaste in ein Kontextmenü klickt, welches die Grenzen des Formulars überschreitet, die Verarbeitung des Scrollens des Charts und der Trading Levels deaktiviert ist. Der Test für das Hinzufügen eines Elementes zu dem Chart war erfolgreich. Von jetzt an wird ein Kontextmenü nur noch angezeigt, wenn der Anwender dieses auswählt. Entfernen Sie das Anzeigen aus der CProgram::CreateTradePanel() Methode in der Anwendungsklasse (entsprechend dem nachfolgendem Programmcode).
m_contextmenu.Show(); // <<< Diese Programmzeile muss entfernt werden
Bezeichner für die externe und interne Verwendung
Wir schreiten nun mit dem Prozess für die Handhabung eines Klicks mit der linken Maustaste auf einen Menüpunkt fort.
Unsere nächste Aufgabe besteht darin, ein Kontextmenü über einen Klick mit der Maustaste anzeigen zu lassen, unter der Bedingung, dass das Kontextmenü mit eingeschlossen ist. Der zweite Klick muss dieses wieder verstecken. Eine solche Handhabung wird sowohl in der CMenuItem Klasse des Menüpunktes und in der CContextMenu Klasse des Kontextmenüs vorhanden sein. Die Sache ist jetzt die, dass das Kontextmenü Zugriff auf das Element hat, zu welchem es hinzugefügt worden ist (der vorherige Knotenpunkt) und der Menüpunkt, der das Kontextmenü enthält, keinen direkten Zugriff darauf hat. Der Pointer des Kontextmenüs kann nicht in der CMenuItem Klasse erzeugt werden. Das liegt daran, dass die ContextMenu.mqh Datei in der MenuItem.mqh Datei miteinbezogen worden ist, was zu Kompilierungsfehlern führt. Das ist der Grund warum wir das Anzeigen des Kontextmenüs in der CContextMenu Klasse durchführen. Der Handler in der CMenuItem Klasse ist für Hilfsfunktionen. Er generiert ein benutzerdefiniertes Event, in welchem spezielle Informationen an das Kontextmenü, über den Menüpunkt, auf welchen geklickt wurde, gesendet werden. Nebenher müssen wir auch noch das Kontexmenü verstecken, wenn ein Mausklick außerhalb des Bereichs des Kontextmenüs stattgefunden hat, so wie es auch in den MetaTrader Terminals und dem MetaEditor Code-Editor geschieht. Dieses gehört zu dem Standardverhalten von Kontextmenüs.
Um dieses realisieren zu können, benötigen wir zusätzliche Bezeichner für benutzerdefinierte Events. Einige von ihnen werden für interne Zwecke in den Bibliotheksklassen entworfen und einige von ihnen für die externe Verarbeitung in den benutzerdefinierten Anwender-Klassen. In unserem Fall ist das CProgram.
Die Events für die interne Verwendung:
- ON_CLICK_MENU_ITEM — Ein Klick auf einen Menüpunkt.
- ON_HIDE_CONTEXTMENUS — Ein Signal für das Verstecken aller Kontextmenüs.
- ON_HIDE_BACK_CONTEXTMENUS — Ein Signal für das Verstecken von Kontextmenüs, die sich unterhalb des aktuellen Menüpunktes befinden. Wir werden dieses später genauer besprechen.
Für die externe Verwendung, erzeugen wir den ON_CLICK_CONTEXTMENU_ITEM Bezeichner, der das Programm darüber informiert, das der Mausklick auf ein Element des Kontextmenüs stattgefunden hat.
Platzieren Sie die aufgelisteten Bezeichner mit den eindeutigen zugewiesenen Werten in die Defines.mqh Datei:
#define ON_CLICK_MENU_ITEM (4) // Klick auf einen Menüpunkt #define ON_CLICK_CONTEXTMENU_ITEM (5) // Klick auf ein Menüpunkt des Kontextmenüs #define ON_HIDE_CONTEXTMENUS (6) // Verstecken aller Kontextmenüs #define ON_HIDE_BACK_CONTEXTMENUS (7) // Verstecken des Kontextmenüs unterhalb des aktuellen Menüpunktes
Erweitern der Kontextmenü Klasse
Die folgenden Variablen und Methoden müssen in der CContextMenu Klasse der Kontextmenüs hinzugefügt werden:
- Für das Abfragen und Setzen des Status des Kontextmenüs.
- Für das Verarbeiten eines Klicks auf einen Menüpunkt.
- Für die Abfrage des Bezeichners und des Index von einem Menüpunkt-Namen Wir wissen bereits, dass das der Grund dafür ist, dass der Index und die Bezeichner einen Teil des Namens der Objekte sind, die mehrere verschiedene Elemente enthalten.
Der nachfolgende Programmcode zeigt die Deklaration und Implementation von alledem, was wir oben aufgelistet haben:
class CContextMenu : public CElement { private: //--- Status des Kontextmenüs bool m_contextmenu_state; public: //--- (1) Abfragen und (2) Setzen des Status des Kontextmenüs bool ContextMenuState(void) const { return(m_context_menu_state); } void ContextMenuState(const bool flag) { m_context_menu_state=flag; } //--- private: //--- Verarbeiten eines Mausklicks auf ein Element, zu welchem dieses Kontextmenü hinzugefügt wurde bool OnClickMenuItem(const string clicked_object); //--- Abfragen (1) des Bezeichners (2) Index aus dem Namen des Menüpunktes int IdFromObjectName(const string object_name); int IndexFromObjectName(const string object_name); }; //+-----------------------------------------------------------------+ //| Die Verarbeitung eines Clicks auf einen Menüpunkt | //+-----------------------------------------------------------------+ bool CContextMenu::OnClickMenuItem(const string clicked_object) { //--- Abbrechen, falls das Kontextmenü bereits geöffnet ist if(m_contextmenu_state) return(true); //--- Abbrechen, falls der Click nicht zu diesem Menüpunkt gehört if(::StringFind(clicked_object,CElement::ProgramName()+"_menuitem_",0)<0) return(false); //--- Abfragen des Bezeichners und des Index aus dem Objekt-Namen int id =IdFromObjectName(clicked_object); int index =IndexFromObjectName(clicked_object); //--- Abbrechen, falls es sich um einen Mausklick handelt, welcher nicht zu einem Menüpunkt gehört, zu welchem dieses Kontextmenü hinzugefügt wurde if(id!=m_prev_node.Id() || index!=m_prev_node.Index()) return(false); //--- Anzeigen des Kontextmenüs Show(); return(true); } //+-----------------------------------------------------------------+ //| Herauslesen des Bezeichners aus dem Objektnamen | //+-----------------------------------------------------------------+ int CContextMenu::IdFromObjectName(const string object_name) { //--- Die ID aus dem Objektnamen herauslesen int length =::StringLen(object_name); int pos =::StringFind(object_name,"__",0); string id =::StringSubstr(object_name,pos+2,length-1); //--- return((int)id); } //+-----------------------------------------------------------------+ //| Herauslesen des Index aus dem Objektnamen | //+-----------------------------------------------------------------+ int CContextMenu::IndexFromObjectName(const string object_name) { ushort u_sep=0; string result[]; int array_size=0; //--- Den Code des Trennzeichens erhalten u_sep=::StringGetCharacter("_",0); //--- Den String aufteilen ::StringSplit(object_name,u_sep,result); array_size=::ArraySize(result)-1; //--- Überprüfung der Überschreitung des Arrays if(array_size-2<0) { ::Print(PREVENTING_OUT_OF_RANGE); return(WRONG_VALUE); } //--- return((int)result[array_size-2]); }
Nun müssen wir nur noch den Aufruf der CContextMenu::OnClickMenuItem() Methode hinzufügen, wenn der CHARTEVENT_OBJECT_CLICK() Event in dem CContextMenu::OnEvent Eventhandler des Kontextmenüs stattgefunden hat:
//--- Verarbeitung eines Klicks mit der linken Maustaste auf ein Objekt if(id==CHARTEVENT_OBJECT_CLICK) { if(OnClickMenuItem(sparam)) return; }
Erweitern der Menüpunkt Klasse
Wenn das Programm einen Klick mit der linken Maustaste auf einen Menüpunkt erkennt, dann übergibt er der CContextMenu::OnClickMenuItem() Methode einen String. Dieser String-Parameter enthält den Namen des grafischen Objektes vom Type Rectangle Label, welches den Hintergrund des Menüpunktes darstellt. Wie Sie sich erinnern, ist die Priorität für einen Klick auf den Hintergrund höher als ein Klick auf jedes andere Objekt, was für fast alle Controls gilt. Dieses garantiert uns, dass ein Klick nicht durch andere Elemente abgefangen wird, was zu einem unerwarteten Verhalten des Programms führen könnte. Wenn zum Beispiel ein Label eines Menüpunktes eine höhere Priorität als der Hintergrund hätte, dann könnte ein Klick auf das Label zu einer Veränderung des Icons führen. Ich möchte Sie daran erinnern, dass wir Label Icons für zwei unterschiedliche Zustände definiert haben. Der Grund dafür ist, dass alle Objekte vom Typ OBJ_BITMAP_LABEL sich standardmäßig so verhalten.
Am Anfang der CContextMenu::OnClickMenuItem() Methode, wird eine Überprüfung des Status des Kontextmenüs durchgeführt. Wenn es bereits aktiviert ist, dann gibt es keinen Grund fortzufahren. Anschließend wird der Name des Objektes überprüft, auf welchen geklickt wurde. Falls es sich um ein Objekt unseres Programms handelt und es einen Hinweis darauf gibt, dass es sich um einen Menüpunkt handelt, dann fahren wir fort. Es wird der Bezeichner und der Index aus dem Namen des Menüpunktes herausgelesen. Für diese Aufgaben haben wir bereits Methoden entwickelt, in welchen alle benötigen Parameter aus dem Objektnamen mit Hilfe der String-Funktionen von MQL herausgelesen werden. Der Bezeichner des Menüpunktes wird mit Hilfe eines doppelten Bindestrichs als Trennzeichen herausgelesen. Um den Index herauszulesen, wird die Zeile mit Hilfe des Unterstrich-symbols in einzelne Teile aufgesplittet(_), welches als Trennzeichen für die Element-Object-Parameter dient.
Erzeugen Sie die OnClickMenuItem() Methode in der CMenuItem Klasse. Der Code unterscheidet sich hier von der Methode, die wir für das Kontextmenü geschrieben haben. Nachfolgend finden Sie die Deklaration und Implementation diese Methode. In dieser Methode gibt es keine Notwendigkeit, die Parameter aus dem Objektnamen zu extrahieren. Es ist hier ausreichend, den Namen des Hintergrundes mit dem Namen des übergebenen Objektes zu vergleichen. Anschließend wird der aktuelle Status des Menüpunktes geprüft. Falls dieser gesperrt ist, dann sind keine weiteren Aktionen notwendig. Anschließen, falls dieses Element ein Kontext-Menü enthält, wird der Status für aktiviert oder deaktiviert zugewiesen. Falls zuvor ein Status eines Kontextmenü aktiviert war, dann sendet das Hauptmodul für das verwalten von Events ein Signal zum Schließen aller Kontextmenüs die geöffnet waren. Dieses gilt für die Fälle, falls mehrere Kontextmenüs durch ein anderes gleichzeitig geöffnet werden. Ein Beispiel hierfür werden wir später in dem Artikel besprechen. Neben dem ON_HIDE_BACK_CONTEXTMENUS Event-Bezeichner, wird auch noch der Menüpunkt-Bezeichner als weiterer Parameter übergeben. Dieser wird dazu verwendet, um herauszufinden, bei welchem Kontextmenü die Schleife gestoppt werden kann.
class CMenuItem : public CElement { //--- Die Verarbeitung eines Klicks auf einen Menüpunkt bool OnClickMenuItem(const string clicked_object); //--- }; //+-----------------------------------------------------------------+ //| Die Verarbeitung eines Kriegs auf einen Menüpunkt | //+-----------------------------------------------------------------+ bool CMenuItem::OnClickMenuItem(const string clicked_object) { //--- Überprüfung über den Objektnamen if(m_area.Name()!=clicked_object) return(false); //--- Abbrechen, falls dieses Element nicht aktiviert wurde if(!m_item_state) return(false); //--- Falls dieses Element ein Kontext-Menü beinhaltet if(m_type_menu_item==MI_HAS_CONTEXT_MENU) { //--- Falls das Dropdown-Menü von diesem Element nicht aktiviert wurde if(!m_context_menu_state) { m_context_menu_state=true; } else { m_context_menu_state=false; //--- Ein Signal für das Schließen von Kontextmenüs senden, die sich unterhalb dieses Elementes befinden ::EventChartCustom(m_chart_id,ON_HIDE_BACK_CONTEXTMENUS,CElement::Id(),0,""); } return(true); } //--- Falls dieses Element kein Kontextmenü besitzt, aber selbst einen Teil eines Kontextmenüs darstellt else { } //--- return(true); }
Erweitern der Hauptklasse für die Verwaltung der Events des grafischen Interfaces
Dieses ist nicht die finale Version der CMenuItem::OnClickMenuItem() Methode und daher werden wir zu ihr später zurückkommen und noch ein paar Erweiterungen vornehmen. Zur Zeit besteht ihre Hauptaufgabe darin, eine Nachricht für das Verstecken der Kontextmenüs über die benutzerdefinierten Events in der CWndEvents Klasse zu senden. Lassen sie uns in dieser Klasse den Zugriff auf eine Methode erzeugen, die über das Event ON_HIDE_BACK_CONTEXTMENUS aufgerufen wird. Lassen Sie sie uns CWndEvents::OnHideBackContextMenus() nennen. Der Programmcode in dieser Methode sieht wie folgt aus:
class CWndEvents : public CWndContainer { private: //--- Verstecken aller Kontextmenüs unterhalb dem auslösenden Element bool OnHideBackContextMenus(void); }; //+-----------------------------------------------------------------+ //| ON_HIDE_BACK_CONTEXTMENUS event | //+-----------------------------------------------------------------+ bool CWndEvents::OnHideBackContextMenus(void) { //--- Falls es sich um ein Signal für das Verstecken von Kontextmenüs unterhalb dem auslösenden Element handelt if(m_id!=CHARTEVENT_CUSTOM+ON_HIDE_BACK_CONTEXTMENUS) return(false); //--- Durchlaufen aller Menüs, angefangen von dem zuletzt aufgerufenen int context_menus_total=CWndContainer::ContextMenusTotal(0); for(int i=context_menus_total-1; i>=0; i--) { //--- Die Pointer zu dem Kontextmenü und deren vorherigen Knotenpunkte CContextMenu *cm=m_wnd[0].m_context_menus[i]; CMenuItem *mi=cm.PrevNodePointer(); //--- Wenn wir bis zu dem auslösenden Element durchgelaufen sind, dann... if(mi.Id()==m_lparam) { //--- ...Falls sein Kontext-Menü keinen Fokus besitzt, dann verstecken if(!cm.MouseFocus()) cm.Hide(); //--- Abbrechen der Schleife break; } else { //--- Verstecken des Kontextmenüs cm.Hide(); } } //--- return(true); }
Die CWndEvents::OnHideBackContextMenus() Methode muss in der Methode für die Behandlung der benutzerdefinierten Events aufgerufen werden.
//+-----------------------------------------------------------------+ //| CHARTEVENT_CUSTOM event | //+-----------------------------------------------------------------+ void CWndEvents::ChartEventCustom(void) { //--- Falls es sich um ein Signal für die Minimierung des Formulars handelt if(OnWindowRollUp()) return; //--- Falls es sich um ein Signal für die Maximierung des Formulars handelt if(OnWindowUnroll()) return; //--- Falls es sich um ein Signal für das Verstecken der Kontextmenüs unterhalb des auslösenden Elementes handelt if(OnHideBackContextMenus()) return; }
Ein vorläufiger Test der Eventhandler
Sobald alle Änderungen durchgeführt worden sind, kompilieren Sie die Datei und laden sie den EA auf einen Chart. Wenn jetzt auf einen unabhängigen Menüpunkt geklickt wird, dann erscheint sein Kontextmenü, falls es zuvor versteckt war und es wird versteckt, falls es zuvor geöffnet war. Zudem wird, wenn ein Kontextmenü geöffnet ist, die Hintergrundfarbe des Menüpunktes festgelegt, damit sie sich nicht mehr verändert, wenn sich der Mauszeiger außerhalb seines Bereiches bewegt.
Abbildung 1. Test über das Anzeigen und das Verstecken eines Kontextmenüs.
Wir fahren nun damit fort, die Interaktion zwischen dem User und einem Kontextmenü weiter abzustimmen. In den meisten Anwendung, wenn ein oder mehrere Kontextmenüs geöffnet sind und ein Mausklick außerhalb deren Grenzen Auftritt, dann werden alle Kontextmenüs gleichzeitig geschlossen. Wir werden hier das gleiche Verhalten nachbilden.
Um diese Funktionalität vollständig testen zu können, werden wir ein weiteres Kontextmenü zu unserem EA hinzufügen. Wir fügen ein Kontextmenü zu den dritten Element des vorhandenen Kontextmenüs hinzu. Dafür weisen Sie dem Dritten Element den MI_HAS_CONTEXT_MENU Typ in der CProgram::CreateContextMenu1() Methode für das Erzeugen des ersten Kontextmenüs in dem items_type[] Array zu:
//--- Array mit Elementtypen ENUM_TYPE_MENU_ITEM items_type[CONTEXTMENU_ITEMS]= { MI_SIMPLE, MI_SIMPLE, MI_HAS_CONTEXT_MENU, MI_CHECKBOX, MI_CHECKBOX };
Lassen Sie uns nun eine Methode für das zweite Kontextmenüs entwickeln. Fügen Sie die zweite Instanz derCContextMenu Klasse der CProgram Klasse hinzu und deklarieren Sie die CreateContextMenu2() Methode:
class CProgram : public CWndEvents { private: //--- Menüpunkt und Kontextmenüs CMenuItem m_menu_item1; CContextMenu m_mi1_contextmenu1; CContextMenu m_mi1_contextmenu2; //--- private: #define MENU_ITEM1_GAP_X (6) #define MENU_ITEM1_GAP_Y (25) bool CreateMenuItem1(const string item_text); bool CreateMI1ContextMenu1(void); bool CreateMI1ContextMenu2(void); };
Das zweite Kontextmenü besitzt sechs Elemente. Es handelt sich um zwei Gruppen von Radio Elementen(MI_RADIOBUTTON), mit je drei Elementen. Nachfolgend ist der Programmcode dieser Methode aufgeführt. Wo liegt der Unterschied zwischen dieser Methode und der Methode für die Erzeugung des ersten Kontextmenüs? Bitte achten Sie darauf, wie wir den Pointer zu dem dritten Element des ersten Kontextmenüs erhalten, zu welchem das zweite Kontextmenü hinzugefügt werden muss. Die dafür verwendete CContextMenu::ItemPointerByIndex() Methode haben wir zuvor erstellt. Solange wir die standardmäßigen Icons für die Radio Elemente verwenden, benötigen Sie keine Arrays. In der CContextMenu::AddItem() Methode geben Sie anstelle dem Pfad zu den Icons leere Werte an. Für die visuelle Trennung der Gruppen von Radio-Elementen wird eine Trendlinie benötigt. Legen Sie dieses nach dem dritten (2) Element in der Liste fest.
Wir haben zuvor schon erwähnt und auch in einem Diagramm gezeigt, dass jede Gruppe von Radio Elementen seinen eigenen eindeutigen Bezeichner besitzen muss. Der Standardwert dieses Parameters ist 0. Aus diesem Grund weisen wir der zweiten Gruppe der Radio Elemente den Bezeichner 1 zu (In der Schleife vom dritten bis zum sechsten). Die CContextMenu Klasse enthält bereits die CContextMenu::RadioItemIdByIndex() Methode für das Setzen des Bezeichners.
Lassen Sie uns auch angeben, welche Radio Elemente in jeder Gruppe hervorgehoben werden sollen, unter Verwendung derCContextMenu::SelectedRadioItem() Methode. In den nachfolgenden Programmcode Wird das zweite Radio Element (Index 1) der ersten Gruppe Hervorgehoben und In der zweiten Gruppe wird das dritte Radio Element (Index 2) hervorgehoben.
//+-----------------------------------------------------------------+ //| Erzeugt das Kontextmenü 2 | //+-----------------------------------------------------------------+ bool CProgram::CreateMI1ContextMenu2(void) { //--- 6 Elemente in dem Kontextmenü #define CONTEXTMENU_ITEMS2 6 //--- Abspeichern des Fenster-Pointers m_mi1_contextmenu2.WindowPointer(m_window); //--- Den Pointer des vorherigen Knotenpunktes speichern m_mi1_contextmenu2.PrevNodePointer(m_mi1_contextmenu1.ItemPointerByIndex(2)); //--- Array Mit den Namen der Elemente string items_text[CONTEXTMENU_ITEMS2]= { "ContextMenu 2 Item 1", "ContextMenu 2 Item 2", "ContextMenu 2 Item 3", "ContextMenu 2 Item 4", "ContextMenu 2 Item 5", "ContextMenu 2 Item 6" }; //--- Festlegen der Eigenschaften bevor er erzeugt wird m_mi1_contextmenu2.XSize(160); m_mi1_contextmenu2.ItemYSize(24); m_mi1_contextmenu2.AreaBackColor(C'240,240,240'); m_mi1_contextmenu2.AreaBorderColor(clrSilver); m_mi1_contextmenu2.ItemBackColorHover(C'240,240,240'); m_mi1_contextmenu2.ItemBackColorHoverOff(clrLightGray); m_mi1_contextmenu2.ItemBorderColor(C'240,240,240'); m_mi1_contextmenu2.LabelColor(clrBlack); m_mi1_contextmenu2.LabelColorHover(clrWhite); m_mi1_contextmenu2.SeparateLineDarkColor(C'160,160,160'); m_mi1_contextmenu2.SeparateLineLightColor(clrWhite); //--- Hinzufügen von Menüpunkten zu dem Kontextmenü for(int i=0; i<CONTEXTMENU_ITEMS2; i++) m_mi1_contextmenu2.AddItem(items_text[i],"","",MI_RADIOBUTTON); //--- Trennlinie nach dem dritten Element m_mi1_contextmenu2.AddSeparateLine(2); //--- Setzen eines eindeutigen Bezeichners (1) für die zweite Gruppe for(int i=3; i<6; i++) m_mi1_contextmenu2.RadioItemIdByIndex(i,1); //--- Auswählen der Radio Elemente in beiden Gruppen m_mi1_contextmenu2.SelectedRadioItem(1,0); m_mi1_contextmenu2.SelectedRadioItem(2,1); //--- Erzeugen eines Kontextmenüs if(!m_mi1_contextmenu2.CreateContextMenu(m_chart_id,m_subwin)) return(false); //--- Hinzufügen des Pointers des Elementes zu der Basis CWndContainer::AddToElementsArray(0,m_mi1_contextmenu2); return(true); }
Der Aufruf der CProgram::CreateContextMenu2() Methode findet in der CProgram::CreateTradePanel() Methode statt.
Test von verschiedenen Kontextmenüs und eine feinere Abstimmung
Nachfolgend wir das Ergebnis nach dem Kompilieren der Dateien und dem Laden des EAs auf den Charts dargestellt.
Abbildung 2. Test von verschiedenen Kontextmenüs
Falls beide Kontextmenüs geöffnet sind und auf das Element geklickt wird, was das erste Menü geöffnet hat, werden beide Menüs geschlossen. Dieses Verhalten unterliegt der CWndEvents::OnHideBackContextMenus() Methode, die wir oben besprochen haben. Aber wenn wir auf den Chart des Formularkopfes (Header) klicken, dann werden die Kontextmenüs nicht geschlossen. Daran werden wir nun arbeiten.
Die Position des Mauszeigers (Fokus) wird in dem OnEvent() Eventhandler der Kontextmenü Klasse (CContextMenu) definiert. Daher werden wir auch in dem Haupt-Eventhandler (in der CWndEvents Klasse) ein Signal für das Schließen aller offenen Kontextmenüs senden. Für diese Aufgabe haben wir die folgende Lösung:
1. Wenn das Event für die Bewegung des Mauszeigers (CHARTEVENT_MOUSE_MOVE) stattfindet, dann enthält der String Parametersparam den Status der linken Maustaste.
2. Anschließend, nachdem wir den Fokus der Maus identifiziert haben, führen wir eine Überprüfung des aktuellen Status des Kontextmenüs und der linken Maustaste durch. Falls das Kontextmenü aktiviert und die Maustaste gedrückt wurde, fahren wir mit der nächsten Überprüfung fort, wo die aktuelle Mausposition in Relation zu diesem Kontextmenü und dem vorherigen Knotenpunkt identifiziert wird.
3. Falls sich der Mauszeiger in keinem der beiden Bereiche befindet, muss auch kein Signal für das Schließen aller Kontextmenüs gesendet werden. Falls sich der Mauszeiger außerhalb der Bereiche dieser Elemente befindet, müssen wir überprüfen ob es irgendwelche Kontextmenüs gibt die geöffnet wurden.
4. Dafür gehen wie die Liste der Kontextmenüs durch, um herauszufinden, ob dieses ein Element enthält, welches sein eigenes Kontextmenü besitzt. Falls es ein solches Element gibt, wird geprüft, ob es aktiviert wurde. Wenn sich herausstellt, dass das Kontextmenü aktiviert wurde, könnte sich der Mauszeiger in seinem Bereich befinden. Das bedeutet, dass ein Signal für das Schließen aller Kontextmenüs von diesem Element nicht gesendet werden darf. Falls der Fall Auftritt, dass das aktuelle Kontextmenü zuletzt geöffnet wurde und in allen anderen Menüs die Bedingungen für das Senden eines Signals nicht zutreffen, es definitiv zutrifft, dass sich der Mauszeiger außerhalb der Bereiche von allen aktivierten Kontextmenüs befindet.
5. Hier können die benutzerdefinierten EventsON_HIDE_CONTEXTMENUS generiert werden.
Wie wir sehen können, ist der Schlüssel für die Lösung, dass alle Kontextmenüs nur geschlossen werden können, wenn sich der Mauszeiger (bei gedrückter linker Maustaste) außerhalb des Bereichs des zuletzt aktivierte Kontextmenüs und außerhalb des Bereichs des Elementes befindet, welches dieses aufgerufen hat.
Der nachfolgende Programmcode beschreibt diese Logik. The CContextMenu::CheckHideContextMenus() method was dedicated to that.
class CContextMenu : public CElement { private: //--- Prüfen der Bedingungen für das Schließen aller Kontextmenüs void CheckHideContextMenus(void); //--- }; //+-----------------------------------------------------------------+ //| Event handler | //+-----------------------------------------------------------------+ void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { if(id==CHARTEVENT_MOUSE_MOVE) { //--- Abbrechen, falls das Element versteckt ist if(!CElement::m_is_visible) return; //--- Erhalten des Focus int x=(int)lparam; int y=(int)dparam; CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); //--- Falls das Kontextmenü aktiviert ist und die linke Maustaste betätigt wurde if(m_context_menu_state && sparam=="1") { //--- Prüfen der Bedingungen für das Schließen aller Kontextmenüs CheckHideContextMenus(); return; } //--- return; } } //+-----------------------------------------------------------------+ //| Prüfen der Bedingungen für das Schließen aller Kontextmenüs | //+-----------------------------------------------------------------+ void CContextMenu::CheckHideContextMenus(void) { //--- Abbrechen, falls sich der Mauszeiger in dem Bereich des Kontextmenüs oder in dem Bereich des vorherigen Knotenpunktes befindet if(CElement::MouseFocus() || m_prev_node.MouseFocus()) return; //--- Falls sich der Mauszeiger außerhalb des Bereiches dieser Elemente befindet, dann... // ... muss überprüft werden, ob es offene Kontextmenüs gibt, die anschließend aktiviert wurden //--- Dafür laufen wir die Liste der Kontextmenüs durch ... // ... für die Identifizierung, ob es ein Menüelement welches ein Kontextmenü besitzt int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) { //--- Falls es ein solches Element gibt, prüfen, ob das Kontextmenü geöffnet ist. // Falls es geöffnet ist, dann wird kein Signal für das Schließen aller Kontextmenü von diesem Element aus gesendet, da... // ... die Möglichkeit besteht, dass sich der Mauszeiger in einem Bereich eines nachfolgenden befindet und dieses muss überprüft werden. if(m_items[i].TypeMenuItem()==MI_HAS_CONTEXT_MENU) if(m_items[i].ContextMenuState()) return; } //--- Ein Signal für das Verstecken aller Kontextmenüs senden ::EventChartCustom(m_chart_id,ON_HIDE_CONTEXTMENUS,0,0,""); }
Nun muss das ON_HIDE_CONTEXTMENUS Event in dem Haupt-Eventhandler der zu entwickelnden Bibliothek in der CWndEvents Klasse empfangen werden. Lassen Sie uns dafür eine Methode schreiben und sie OnHideContextMenus() nennen. Sie ist recht einfach, da sie lediglich die Liste der privaten Kontextmenüs durchgehen muss und diese dann versteckt.
Die Deklaration und Implementation der CWndEvents::OnHideContextMenus() Methode wird in den nachfolgenden Programmcode gezeigt:
class CWndEvents : public CWndContainer { private: //--- Verstecken aller Kontextmenüs bool OnHideContextMenus(void); }; //+-----------------------------------------------------------------+ //| ON_HIDE_CONTEXTMENUS event | //+-----------------------------------------------------------------+ bool CWndEvents::OnHideContextMenus(void) { //--- Falls es sich um ein Signal für das Verstecken aller Kontextmenüs handelt if(m_id!=CHARTEVENT_CUSTOM+ON_HIDE_CONTEXTMENUS) return(false); //--- int cm_total=CWndContainer::ContextMenusTotal(0); for(int i=0; i<cm_total; i++) m_wnd[0].m_context_menus[i].Hide(); //--- return(true); }
Nach der Kompilierung der Dateien und dem Laden des EAs auf einen Chart, können Sie feststellen, dass aktivierte Kontextmenüs geschlossen werden, falls sich bei einem Mausklick der Mauszeiger außerhalb derer Bereiche befindet.
Wir müssen noch einen weiteren spürbaren Designfehler beseitigen. Sehen Sie sich den nachfolgenden Screenshot an: Es zeigt eine Situation, wo sich der Mauszeiger in dem Bereich des ersten Kontext-Menüs befindet, aber außerhalb des Bereiches von dem Menüelement, von welchem das zweite Kontextmenü aus aufgerufen wurde. Normalerweise werden alle Menüelemente, die sich hinter dem Menüelemente befinden, auf welchen der Mauszeiger gerade zeigt, geschlossen. Lassen Sie uns hierfür den Programmcode schreiben.
Abbildung 3. In solch einer Situation müssen alle Kontextmenüs auf der rechten Seite versteckt werden.
Wie nennen die Methode CContextMenu::CheckHideBackContextMenus(). Ihre Logik haben wir gerade eben schon besprochen und wir können daher direkt mit der Implementation fortfahren: Wenn alle Bedingungen zutreffen, dann wird ein ON_HIDE_BACK_CONTEXTMENUS Event generiert.
class CContextMenu : public CElement { private: //--- Überprüfung, für das Schließen aller Kontextmenü die nach dem Aktuellen geöffnet wurden void CheckHideBackContextMenus(void); //--- }; //+-----------------------------------------------------------------+ //| Event handler | //+-----------------------------------------------------------------+ void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { if(id==CHARTEVENT_MOUSE_MOVE) { //--- Abbrechen, falls das Element versteckt ist if(!CElement::m_is_visible) return; //--- Erhalten des Focus int x=(int)lparam; int y=(int)dparam; CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); //--- Falls das Kontextmenü aktiviert ist und die linke Maustaste betätigt wurde if(m_context_menu_state && sparam=="1") { //--- Prüfen der Bedingungen für das Schließen aller Kontextmenüs CheckHideContextMenus(); return; } //--- Überprüfung, für das Schließen aller Kontextmenü die nach dem Aktuellen geöffnet wurden CheckHideBackContextMenus(); return; } } //+------------------------------------------------------------------+ //| Überprüfung der Bedingungen für das Schließen aller Kontextmenüs | //| welche nach diesem geöffnet wurden | //+------------------------------------------------------------------+ void CContextMenu::CheckHideBackContextMenus(void) { //--- Durchlaufen aller Menüelemente int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) { //--- Falls es sich bei dem Element um ein Kontext-Menü handelt und es aktiviert ist if(m_items[i].TypeMenuItem()==MI_HAS_CONTEXT_MENU && m_items[i].ContextMenuState()) { //--- Falls sich der Fokus in dem Kontextmenü befindet, aber nicht in diesem Element if(CElement::MouseFocus() && !m_items[i].MouseFocus()) //--- Sende ein Signal um alle Kontextmenüs zu verstecken, die nach diesem geöffnet wurden ::EventChartCustom(m_chart_id,ON_HIDE_BACK_CONTEXTMENUS,CElement::Id(),0,""); } } }
Zuvor wurde schon die OnHideBackContextMenus() Methode in der CWndEvents Klasse für das Verarbeiten des ON_HIDE_BACK_CONTEXTMENUS Events geschrieben. Daher können wir jetzt mit dem Kompilieren der Dateien und dem Test des EAs fortfahren. Falls alles korrekt programmiert wurde, dann werden sich die Kontextmenüs entsprechend der Bewegungen des Mauszeigers und entsprechend der programmierten Logik verhalten.
Der schwierigste Teil liegt nun hinter uns, aber wir sind noch nicht fertig. Die Eventhandler müssen noch so eingerichtet werden, dass wenn auf irgendeinen Kontextmenü geklickt wird, eine Nachricht mit Parametern zu der benutzerdefinierten Klasse der Anwendung gesendet wird (CProgram). Diese Parameter erlauben uns festzustellen, auf welches Menüelement genau geklickt wurde. Hierdurch kann der Entwickler der Anwendung dem Klick auf ein Menüelement eine entsprechende Funktion zuweisen. Nebenher muss noch das umschalten der Zuständen einer Checkbox und von Radio Elementen eines Kontextmenüs eingerichtet werden.
Der Teil für die Bedingung, wenn ein Menüelement kein Kontextmenü besitzt aber ein Teil von ihm ist, ist in der OnClickMenuItem() Methode der CMenuItem Klasse immer noch leer. Das Benutzerdefinierte Event ON_CLICK_MENU_ITEM wird von hier aus gesendet. Diese Nachricht enthält die folgenden zusätzlichen Parameter:
- Der Index der gemeinsamen Liste.
- Den Bezeichner des Elementes
- Eine Zeile, die wie folgt gebildet wird:
- Im Namen des Programms;
- Einem Hinweis auf Checkbox oder Radio Element;
- Falls es sich um einen Radio Element handelt, dann enthält die Zeile auch den Bezeichner des Elementes.
Wie Sie sehen können, können wir auch wenn die EventChartCustom() Funktion nicht ausreichend ist, mit einem String die benötigten Parameter für die exakte Identifikation bilden. Wie auch bei den Namen der grafischen Objekte, werden die Parameter mit dem Unterstrich "_" getrennt.
Der Status der Checkbox und des Radio-Elementes wird in diesem Block ebenfalls geändert Nachfolgend sehen Sie eine verkürzte Version der CMenuItem::OnClickMenuItem() Methode. Sie zeigt nur den Programmcode, der zu dem Block else hinzugefügt werden muss.
//+-----------------------------------------------------------------+ //| Klick auf den Header eines Elementes | //+-----------------------------------------------------------------+ bool CMenuItem::OnClickMenuItem(const string clicked_object) { //--- Überprüfung über den Objektnamen //--- Abbrechen, falls dieses Element nicht aktiviert wurde //--- Falls dieses Element ein Kontext-Menü beinhaltet //... //--- Falls dieses Element kein Kontextmenü besitzt, aber selbst einen Teil eines Kontextmenüs darstellt else { //--- Nachrichten Prefix mit dem Programmnamen string message=CElement::ProgramName(); //--- Falls es sich um eine Checkbox handelt, dann ändere den Status if(m_type_menu_item==MI_CHECKBOX) { m_checkbox_state=(m_checkbox_state)? false : true; m_icon.Timeframes((m_checkbox_state)? OBJ_NO_PERIODS : OBJ_ALL_PERIODS); //--- Der Nachricht hinzufügen, dass es sich um eine Checkbox handelt message+="_checkbox"; } //--- Falls es sich um ein Radio Element handelt, dann den Status ändern else if(m_type_menu_item==MI_RADIOBUTTON) { m_radiobutton_state=(m_radiobutton_state)? false : true; m_icon.Timeframes((m_radiobutton_state)? OBJ_NO_PERIODS : OBJ_ALL_PERIODS); //--- Der Nachricht hinzufügen, dass es sich um ein Radio-Element handelt message+="_radioitem_"+(string)m_radiobutton_id; } //--- Eine Nachricht darüber senden ::EventChartCustom(m_chart_id,ON_CLICK_MENU_ITEM,m_index,CElement::Id(),message); } //--- return(true); }
Der benutzerdefinierter Event mit dem ON_CLICK_MENU_ITEM wird mit dem Handler der Kontextmenü Klasse (CContextMenu) bezeichnet. Wir benötigen weitere Methoden für das Extrahieren des Bezeichners aus dem String-Parameter des Events falls auf ein Radio Element geklickt wurde und für die Abfrage des Index in Abhängigkeit zu welcher Gruppe dieses Radio Element gehört. Nachfolgend finden Sie den Programmcode für diese Methoden.
Da die Extrahierung des Bezeichners aus dem String-Parameter davon abhängig ist, wie die Struktur des Strings aussieht, wird die CContextMenu::RadioIdFromMessage() Methode noch eine zusätzliche Überprüfung für die Korrektheit des Strings und für die Überprüfung der Überschreitung der Array Größe besitzen..
Am Anfang der CContextMenu::RadioIndexByItemIndex() Methode, welche dazu gedacht ist, den Index des Radio Elementes über den Hauptindex zurückzugeben, fragen wir den Radio-Element-Bezeichner ab. Wir verwenden die zuvor geschriebene CContextMenu::RadioItemIdByIndex() Methode. Anschließend zählen wir in einer Schleife die Radio Elemente mit diesem Bezeichner. Wenn wir bis zu dem Radio Element durchgelaufen sind, dessen genereller Index mit dem Wert des übergebenen Index uebereinstimmt, dann speichern wir den Wert des Zählers und stoppen die Schleife. Das bedeutet, dass der letzte Wert des Zählers den Index darstellt, den wir zurück geben müssen.
class CContextMenu : public CElement { private: //--- Abfrage (1) des Bezeichners und (2) Index von der Nachricht des Radio Elementes int RadioIdFromMessage(const string message); int RadioIndexByItemIndex(const int index); //--- }; //+------------------------------------------------------------------+ //| Extrahiert den Bezeichner aus der Nachricht für das Radio Element| //+------------------------------------------------------------------+ int CContextMenu::RadioIdFromMessage(const string message) { ushort u_sep=0; string result[]; int array_size=0; //--- Den Code des Trennzeichens erhalten u_sep=::StringGetCharacter("_",0); //--- Den String aufteilen ::StringSplit(message,u_sep,result); array_size=::ArraySize(result); //--- Falls ich die Struktur der Nachricht von der erwarteten Struktur unterscheidet if(array_size!=3) { ::Print(__FUNCTION__," > Wrong structure in the message for the radio item! message: ",message); return(WRONG_VALUE); } //--- Schutz vor Überschreitung der Array-Größe if(array_size<3) { ::Print(PREVENTING_OUT_OF_RANGE); return(WRONG_VALUE); } //--- Rückgabe der ID des Radio Elementes return((int)result[2]); } //+---------------------------------------------------------------------+ //| Gibt den Index des Radio Elementes über den generellen Index zurück | //+---------------------------------------------------------------------+ int CContextMenu::RadioIndexByItemIndex(const int index) { int radio_index =0; //--- Abfrage der ID des Radio Elementes über den generellen Index int radio_id =RadioItemIdByIndex(index); //--- Element-Zähler von der benötigten Gruppe int count_radio_id=0; //--- Durchgehend der Liste int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) { //--- Falls es sich nicht um ein Radio Element handelt, dann mit dem nächsten fortfahren if(m_items[i].TypeMenuItem()!=MI_RADIOBUTTON) continue; //--- Wenn die Bezeichner übereinstimmen if(m_items[i].RadioButtonID()==radio_id) { //--- Falls die Indizes zusammenpassen // Abspeichern des Zählers und Abschließen der Schleife if(m_items[i].Index()==index) { radio_index=count_radio_id; break; } //--- Erhöhung des Zählers count_radio_id++; } } //---Index zurückgeben return(radio_index); }
Lassen Sie uns nun die CContextMenu::ReceiveMessageFromMenuItem() Methode für die Verarbeitung des ON_CLICK_MENU_ITEM benutzerdefinierten Events von dem Menüelement entwerfen. Die folgenden Event-Parameter müssen dieser Methode übergeben werden: Der Bezeichner, Index und String Nachricht. Am Anfang der Methode in werden die Bedingungen überprüft, ob es sich um eine Nachricht von unserem Programm handelt und ob die Bezeichner übereinstimmen. Falls die Bedingungen zutreffen und es eine Nachricht eines Radio Element es ist, wird in der Gruppe der Radio Elemente, welche über den Bezeichner definiert ist, das entsprechende Element über den Index geschaltet. Wir können den Bezeichner und den Index über die Methoden, die wir weiter oben entworfen haben, abfragen.
Unabhängig von den Typ eines Menüelements, von welchem diese Nachricht aus gesendet wurde, wird im Falle der erfolgreichen Überprüfung des Programmnamens und dem Vergleich der Bezeichner, das benutzerdefinierte Event ON_CLICK_CONTEXTMENU_ITEM gesendet. Dieses geht an den Eventhandler in der CProgram Klasse der benutzerdefinierten Anwendung. Zusammen mit dieser Nachricht werden noch die folgenden Parameter gesendet: (1) Bezeichner, (2) Der generelle Index in der Liste der Kontextmenüs (3) und der angezeigte Texte des Elementes.
Am Ende der Methode, wird unabhängig von der ersten Überprüfung (1) Das Kontextmenü versteckt, (2) Das Formular entsperrt (3) und ein Signal für das Schließen aller Kontextmenüs gesendet.
class CContextMenu : public CElement { private: //--- Empfangen einer Nachricht von einem Menüelement void ReceiveMessageFromMenuItem(const int id_item,const int index_item,const string message_item); //--- } //+-----------------------------------------------------------------+ //| Event handler | //+-----------------------------------------------------------------+ void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Verarbeiten des ON_CLICK_MENU_ITEM Events if(id==CHARTEVENT_CUSTOM+ON_CLICK_MENU_ITEM) { int item_id =int(dparam); int item_index =int(lparam); string item_message =sparam; //--- Empfangen einer Nachricht von einem Menüelement ReceiveMessageFromMenuItem(item_id,item_index,item_message); return; } } //+-----------------------------------------------------------------+ //| Empfangen einer Nachricht von einem Menüelement | //+-----------------------------------------------------------------+ void CContextMenu::ReceiveMessageFromMenuItem(const int id_item,const int index_item,const string message_item) { //--- Falls es einen Hinweis darauf gibt, dass diese Nachricht von diesem Programm gesendet wurde und die Element ID uebereinstimmt if(::StringFind(message_item,CElement::ProgramName(),0)>-1 && id_item==CElement::Id()) { //--- Falls auf ein Radio Element geklickt wurde if(::StringFind(message_item,"radioitem",0)>-1) { //--- Abfrage der Radio Element ID von der gesendeten Nachricht int radio_id=RadioIdFromMessage(message_item); //--- Abfrage des Radio Element Index über den generellen Index int radio_index=RadioIndexByItemIndex(index_item); //--- Schaltet das Radio Element SelectedRadioItem(radio_index,radio_id); } //--- Eine Nachricht darüber senden ::EventChartCustom(m_chart_id,ON_CLICK_CONTEXTMENU_ITEM,index_item,id_item,DescriptionByIndex(index_item)); } //--- Verstecken des Kontextmenüs Hide(); //--- Die Form entsperren m_wnd.IsLocked(false); //--- Ein Signal für das Verstecken aller Kontextmenüs senden ::EventChartCustom(m_chart_id,ON_HIDE_CONTEXTMENUS,0,0,""); }
Test für das Empfangen von Nachrichten in der benutzerdefinierten Klasse der Anwendung
Jetzt können wir den Empfang einer solchen nachricht in dem Handler derCProgram Klasse testen. Fügen Sie dazu den hier gezeigten Code hinzu:
//+-----------------------------------------------------------------+ //| Event handler | //+-----------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { if(id==CHARTEVENT_CUSTOM+ON_CLICK_CONTEXTMENU_ITEM) { ::Print(__FUNCTION__," > index: ",lparam,"; id: ",int(dparam),"; description: ",sparam); } }
Kompilieren Sie die Dateien und laden Sie den EA auf einen Chart. Wenn auf Menüelemente geklickt wird, dann erhalten Sie Nachrichten mit den Parametern dieser Elemente in dem Journal des Terminals:
2015.10.23 20:16:27.389 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 4; id: 2; description: ContextMenu 1 Item 5 2015.10.23 20:16:10.895 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 0; id: 3; description: ContextMenu 2 Item 1 2015.10.23 19:27:58.520 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 5; id: 3; description: ContextMenu 2 Item 6 2015.10.23 19:27:26.739 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 2; id: 3; description: ContextMenu 2 Item 3 2015.10.23 19:27:23.351 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 3; id: 3; description: ContextMenu 2 Item 4 2015.10.23 19:27:19.822 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 4; id: 2; description: ContextMenu 1 Item 5 2015.10.23 19:27:15.550 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 1; id: 2; description: ContextMenu 1 Item 2
Wir haben die Entwicklung des Hauptteils der CContextMenu Klasse für die Erzeugung eines Kontextmenüs abgeschlossen. Sie wird wahrscheinlich noch Erweiterungen benötigen, aber wir werden erst später darauf zurückkommen, wenn sich eindeutige Probleme bei den Tests ergeben. Kurz gesagt, wir werden in der natürlichen Reihenfolge vorgehen, da es so einfacher ist, das Material zu studieren.
Schlussfolgerung
In diesem Artikel haben wir die Klasse der Elemente, die wir in dem vorherigen Artikel erzeugt haben, erweitert. Nun haben wir alles vorliegen, um das Hauptmenü-Element zu entwickeln. Wir werden dieses in den nächsten Artikel bearbeiten.
Die unten aufgelisteten Archive enthalten die Dateien der Bibliothek zu dem aktuellen Stand der Entwicklung, sowie Bilder und Dateien der besprochenen Programme (Der EA, die Indicatoren und das Skript). Sie können für Tests in dem MetaTrader 4 und MetaTrader 5 Terminal heruntergeladen werden. Wenn Sie fragen zur Verwendung dieses Materials haben, dann können Sie zunächst auf die detaillierte Beschreibung in dem Artikel zu dieser Bibliothek zurückgreifen oder Sie stellen Ihre Frage(n) in den Kommentaren zu diesem Artikel.
Liste der Artikel (Kapitel) des zweiten Teils:
- Grafische Interfaces II:Das Menu-Item-Element(Kapitel 1)
- Grafische Interfaces II: Die Trennlinien und Context-Menüelemente (Kapitel 2)
- Grafische Interfaces II: Einrichtung des Eventhandlers für die Bibliothek (Kapitel 3)
- Grafische Interfaces II: Das Hauptmenü Element (Kapitel 4)
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/2204





- 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.