Grafische Interfaces II: Einrichtung des Eventhandlers für die Bibliothek (Kapitel 3)

Anatoli Kazharski | 30 Juni, 2016

Inhalt

 

 

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:

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:

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: 

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.

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

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.

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:

  1. Der Index der gemeinsamen Liste. 
  2. Den Bezeichner des Elementes
  3. 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: