English Русский 中文 Español 日本語 Português
Graphische Interfaces III: Einfache und multifunktionale Buttons (Kapitel 1)

Graphische Interfaces III: Einfache und multifunktionale Buttons (Kapitel 1)

MetaTrader 5Beispiele | 1 Juli 2016, 15:12
1 103 0
Anatoli Kazharski
Anatoli Kazharski

Inhalt

Einleitung

In den vorangegangenen zwei Teilen dieser Serie haben wir einige Punkte über die Entwicklung einer Bibliotheksstruktur für die Erzeugung von grafischen Interfaces und grundlegende Mechanismen für die Verwaltung von Objekten besprochen.

In dem zweiten Teil dieser Serie haben wir ein Beispiel für die Erzeugung eines Controls besprochen und wie man ihn mit der Bibliotheks-Engine verbindet. Das Beispiel war ziemlich schwierig. Hauptmenüs und Kontextmenüs gehören zu den schwierigsten Controls.

Dieser Teil des Artikels ist um einiges einfacher als der Vorherige. Hier besprechen wir lediglich das Control "Button".

Ein Button gehört zu den einfachsten Controls, mit welchem ein User interagieren kann. Gleichzeitig gibt es aber auch mehrere Wege der Implementation. In diesem Teil des Artikels werden wir drei Klassen für die Erzeugung von Buttons besprechen, in die unterschiedliche Ebenen der Komplexität besitzen.

  • Einfacher Button. The CSimpleButton class.
  • Icon Button. Die CIconButton Klasse.
  • Split Button. Die CSplitButton Klasse.

Zuzüglich werden wir noch drei Klassen für die Erzeugung von Gruppen von interagierenden Buttons implementieren.

  • Gruppe von einfachen Buttons Die CButtonsGroup Klasse.
  • Gruppe von Icon-Buttons Die CIconButtonsGroup Klasse.
  • Gruppe von Radio-Buttons Die CRadioButtons Klasse.

Wir werden zudem eine erweiterte Funktionalität des Kontextmenüs besprechen. Die CWindow Form-Klasse enthält ein weiteres Feld mit einer Methode, mit welcher wir definieren können, welches Control in dem Moment der Aktivierung des Formulars blockiert wird. Dies erlaubt es uns einen Zustand zu erzeugen, bei dem die Form nur über das Control freigegeben werden kann welches es auch blockiert hat.

Wie werden die Charakteristiken der Methoden aller Controls nicht weiter diskutieren, da wir sie schon in den vorherigen Artikeln besprochen haben. Diese Methoden werden in den nachfolgenden Programmcodes lediglich als Deklaration in den Körpern der Klassen dargestellt.

 


Entwicklung einer Klasse für die Erzeugung eines einfachen Buttons

Lassen Sie uns mit einem einfachen Button anfangen. Wir haben schon bereits eine Klasse für die Erzeugung eines einfachen Objektes vom TypCButton in der Objects.mqh Datei. CChartObjectButton ist die Basisklasse, welche dazu benutzt werden kann, ein grafisches Objekt vom Typ OBJ_BUTTON zu erstellen. Die Eigenschaften von diesem Objekt besitzen bereits zwei Zustände, an und aus. Die graphische Repräsentation kann auch zwei Optionen besitzen, in Abhängigkeit ob die Darstellung des Objektes aktiviert ist oder nicht. In beiden Modi, ist die Farbe des Buttons leicht dunkler, in dem Moment wo er gedrückt ist.

Dieses Objekt kann manuell zu einem Chart über das Hauptmenü hinzugefügt werden Einfügen -> Objects -> Graphische Objekte -> Button. Die Parameter eines grafischen Objektes können auch manuell über das Eigenschaftenfenster des grafischen Objektes geändert werden:

Abbildung  1. Das Eigenschaftenfenster eines grafischen Objektes.

Abbildung 1. Das Eigenschaftenfenster eines grafischen Objektes.

 

Anschließend erstellen wir in dem Controls Verzeichnis die Datei SimpleButton.mqh. In dieser Datei erzeugen wir dieCSimpleButton Klasse mit den Feldern und Methoden der Standard-Controls, welche wir schon im Detail in den vorherigen Artikel besprochen haben.

//+------------------------------------------------------------------+
//|                                                 SimpleButton.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
//+------------------------------------------------------------------+
//| Klasse für die Erzeugung eines einfachen Buttons                 |
//+------------------------------------------------------------------+
class CSimpleButton : public CElement
  {
private:
   //--- Ein Pointer zu der Form zu welchem das Element hinzugefügt worden ist 
   CWindow          *m_wnd;
   //---
public:
                     CSimpleButton(void);
                    ~CSimpleButton(void);
   //--- Speichert den Pointer
   void              WindowPointer(CWindow &object)          { m_wnd=::GetPointer(object);     }
   //---
public:
   //--- Chart Eventhandler
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Timer
   virtual void      OnEventTimer(void);
   //--- Bewegen des Elementes
   virtual void      Moving(const int x,const int y);
   //--- (1) Anzeigen, (2) verstecken, (3) zurücksetzen, (4) löschen
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) Setzen, (2) Zurücksetzen der linken Maustaste
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
  };

Lassen Sie uns definieren, welche Eigenschaften wir schon vor der Erstellung des Buttons festlegen können.

  • Größe.
  • Hintergrundfarben für die unterschiedlichen Zustände des Buttons und Farben in Abhängigkeit von der Position des Mauszeigers.
  • Die Rahmenfarben für unterschiedliche Zustände des Buttons.
  • Textfarben.

Manchmal wird auch ein Button benötigt, der automatisch seinen Zustand wieder umkehrt, nachdem er angeklickt wurde. Es gibt auch die Möglichkeit, dass ein Button, nachdem er angeklickt worden ist, in dem eingeschalteten Zustand verbleibt, und erst beim nächsten Klick wieder in seine normalen Zustand zurückkehrt. Wir werden den Button mit zwei Modi zur Auswahl austatten. Die IsPressed() Methode ist dazu da, um herauszufinden, ob sich der Button gerade in einem eingeschalteten oder ausgeschalteten Zustand befindet.

Zudem wird eineMöglichkeit zum Blockieren und Freigeben eines Buttons benötigt, falls der Entwickler einer Anwendung dieses verwenden will. Zum Beispiel kann eine Taste gesperrt werden, falls die Bedingung für die Verwendung der Funktion, die mit diesem Button in Zusammenhang steht, noch nicht erfüllt sind. Der Button ist in dem Moment wieder anklickbar, sobald die Bedingungen erfüllt sind. Wir werden dazu später ein paar Beispiele betrachten.

Lassen Sie uns die Methoden für die Erzeugung eines Buttons der Klasse hinzufügen. Sie sind im Prinzip kaum unterschiedlich zu dem, was wir schon betrachtet haben, und sie finden den Programmcode dieser Methoden in den angehängten Dateien zu diesem Artikel.

class CSimpleButton : public CElement
  {
private:
   //--- Objekt für die Erzeugung eines Buttons
   CButton           m_button;
   //--- Die Eigenschaften des Buttons:
   //    (1) Text, (2) size
   string            m_button_text;
   int               m_button_x_size;
   int               m_button_y_size;
   //--- Die Hintergrundfarbe
   color             m_back_color;
   color             m_back_color_off;
   color             m_back_color_hover;
   color             m_back_color_pressed;
   color             m_back_color_array[];
   //--- Die Farbe des Rahmens
   color             m_border_color;
   color             m_border_color_off;
   //--- Die Textfarbe
   color             m_text_color;
   color             m_text_color_off;
   color             m_text_color_pressed;
   //--- Die Priorität der linken Maustaste
   int               m_button_zorder;
   //--- Der Modi für die zwei Zustände des Buttons
   bool              m_two_state;
   //--- Verfügbar oder blockiert
   bool              m_button_state;
   //---
public:
   //--- Methode für die Erzeugung eines einfachen Buttons
   bool              CreateSimpleButton(const long chart_id,const int subwin,const string button_text,const int x,const int y);
   //---
private:
   bool              CreateButton(void);
   //---
public:
   //--- (1) Festlegung des Modus,
   //    (2) Genereller Zustand des Buttons(Verfügbar oder blockiert)
   void              TwoState(const bool flag)               { m_two_state=flag;               }
   bool              IsPressed(void)                   const { return(m_button.State());       }
   bool              ButtonState(void)                 const { return(m_button_state);         }
   void              ButtonState(const bool state);
   //--- Die Größe des Buttons
   void              ButtonXSize(const int x_size)           { m_button_x_size=x_size;         }
   void              ButtonYSize(const int y_size)           { m_button_y_size=y_size;         }
   //--- (1) Gib den Text des Buttons zurück, (2) Festlegung der Textfarbe
   string            Text(void)                        const { return(m_button.Description()); }
   void              TextColor(const color clr)              { m_text_color=clr;               }
   void              TextColorOff(const color clr)           { m_text_color_off=clr;           }
   void              TextColorPressed(const color clr)       { m_text_color_pressed=clr;       }
   //--- Festlegung der Hintergrundfarbe
   void              BackColor(const color clr)              { m_back_color=clr;               }
   void              BackColorOff(const color clr)           { m_back_color_off=clr;           }
   void              BackColorHover(const color clr)         { m_back_color_hover=clr;         }
   void              BackColorPressed(const color clr)       { m_back_color_pressed=clr;       }
   //--- Setting up the color of the button frame
   void              BorderColor(const color clr)            { m_border_color=clr;             }
   void              BorderColorOff(const color clr)         { m_border_color_off=clr;         }
   //---
  };
//+-----------------------------------------------------------------+
//| Veränderung des Status des Buttons                              |
//+-----------------------------------------------------------------+
void CSimpleButton::ButtonState(const bool state)
  {
   m_button_state=state;
   m_button.State(false);
   m_button.Color((state)? m_text_color : m_text_color_off);
   m_button.BackColor((state)? m_back_color : m_back_color_off);
   m_button.BorderColor((state)? m_border_color : m_border_color_off);
  }

Wir betrachten nun das Event Handling, in dem Moment wo der Button angeklickt wird. Wir fügen einen weiteren Identifizierer ON_CLICK_BUTTON der Defines.mqh Datei für die Erzeugung eines benutzerdefinierten Events hinzu. Dieser wird in allen Klassen verwendet, die sich mit der Erzeugung eines Buttons beschäftigen.

#define ON_CLICK_BUTTON           (8)  // Pressing the button

Jetzt erzeugen wir die CSimpleButton::OnClickButton() Methode um den Klick auf einen Button zu verarbeiten. Zu Beginn der Methode sind zwei Überprüfungen notwendig: Die Überprüfung des Namens des angeklickten Objektes und den Status des Objektes. Negative Ergebnisse führen zu dem Verlassen der Methode. Wenn diese Überprüfungen erfolgreich abgeschlossen worden sind, dann wird festgestellt, in welchem Modus sich der Button befindet. Wenn sich der Button selbst wieder zurück setzen soll, dann wird er in seinen anfänglichen Status gebracht und die Farben werden entsprechend gesetzt. Für den Modus mit 2 Zuständen, bekommt jeder Status zwei Gruppen von Farben. Am Ende der Methode wird mit dem ON_CLICK_BUTTON Identifizierer eine Nachricht gesendet, die das Element identifiziert, den Index des Elementes und den Namen enthält. 

class CSimpleButton : public CElement
  {
private:
   //--- Verarbeitendes Button click events
   bool              OnClickButton(const string clicked_object);
  };
//+-----------------------------------------------------------------+
//| Event handling                                                  |
//+-----------------------------------------------------------------+
void CSimpleButton::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Verarbeitendes Events bei einem Klick mit der linken Maustaste
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      if(OnClickButton(sparam))
         return;
     }
  }
//+-----------------------------------------------------------------+
//| Verarbeiten eines Klicks auf einen Button                       |
//+-----------------------------------------------------------------+
bool CSimpleButton::OnClickButton(const string clicked_object)
  {
//--- Überprüfen des Namens des Objektes
   if(m_button.Name()!=clicked_object)
      return(false);
//--- Falls der Button gesperrt ist
   if(!m_button_state)
     {
      m_button.State(false);
      return(false);
     }
//--- Falls der Modus des Buttons nur einen Zustand hat
   if(!m_two_state)
     {
      m_button.State(false);
      m_button.Color(m_text_color);
      m_button.BackColor(m_back_color);
     }
//--- Falls der Modus des Buttons zwei Zustände besitzt
   else
     {
      //--- Falls die Taste gedrückt ist
      if(m_button.State())
        {
         //--- Veränderung der Farbe des Buttons 
         m_button.State(true);
         m_button.Color(m_text_color_pressed);
         m_button.BackColor(m_back_color_pressed);
         CElement::InitColorArray(m_back_color_pressed,m_back_color_pressed,m_back_color_array);
        }
      //--- Falls der Button nicht geklickt ist
      else
        {
         //--- Veränderung der Farbe des Buttons 
         m_button.State(false);
         m_button.Color(m_text_color);
         m_button.BackColor(m_back_color);
         CElement::InitColorArray(m_back_color,m_back_color_hover,m_back_color_array);
        }
     }
//--- Senden eines Events
   ::EventChartCustom(m_chart_id,ON_CLICK_BUTTON,CElement::Id(),CElement::Index(),m_button.Description());
   return(true);
  }

Wir können jetzt schon einen Test über die Einstellungen eines Buttons auf dem Chart durchführen und Nachrichten in dem Eventhandler der benutzerdefinierten Klasse dieser Applikation erhalten. Wir kopieren uns dazu den Test EA aus dem vorherigen Artikel. Lassen Sie nur das Hauptmenü mit den angefügten Kontextmenüs bestehen. Die Datei mit der CSimpleButton Klasse muss in der WndContainer.mqh Datei mit eingebunden werden. 

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "SimpleButton.mqh"

Anschließend können die Instanzen der CSimpleButton Klass Und die Methoden für die Erzeugung der Buttons in der CProgram Benutzerdefinierten Klasse dieser Anwendung deklariert werden. Lassen Sie uns als Beispiel drei Buttons erzeugen. Zwei von denen kehren nach dem Klick in ihren Normalzustand automatisch zurück, und der Dritte besitzt die Option fixiert zu werden. Aus diesem Grunde besitzt er zwei Zustände (Gedrückt/Nich gedrückt).

class CProgram : public CWndEvents
  {
private:
   //--- Einfache buttons
   CSimpleButton     m_simple_button1;
   CSimpleButton     m_simple_button2;
   CSimpleButton     m_simple_button3;
   //---
private:
#define BUTTON1_GAP_X            (7)
#define BUTTON1_GAP_Y            (50)
   bool              CreateSimpleButton1(const string text);
#define BUTTON2_GAP_X            (128)
#define BUTTON2_GAP_Y            (50)
   bool              CreateSimpleButton2(const string text);
#define BUTTON3_GAP_X            (7)
#define BUTTON3_GAP_Y            (75)
   bool              CreateSimpleButton3(const string text);
  };

Wir betrachten hier nur den Code für einen Button. Bitte beachten Sie den hervorgehobenen Programmcode, wo der Modus für den Button mit den zwei Zuständen aktiviert wird. 

//+-----------------------------------------------------------------+
//| Erzeugung des dritten einfachen buttons                         |
//+-----------------------------------------------------------------+
bool CProgram::CreateSimpleButton3(string button_text)
  {
//--- Übergabe des Panel Objektes
   m_simple_button3.WindowPointer(m_window);
//--- Koordinaten
   int x=m_window.X()+BUTTON3_GAP_X;
   int y=m_window.Y()+BUTTON3_GAP_Y;
//--- Festlegen der Eigenschaften bevor er erzeugt wird
   m_simple_button3.TwoState(true);
   m_simple_button3.ButtonXSize(237);
   m_simple_button3.TextColor(clrBlack);
   m_simple_button3.TextColorPressed(clrBlack);
   m_simple_button3.BackColor(clrLightGray);
   m_simple_button3.BackColorHover(C'193,218,255');
   m_simple_button3.BackColorPressed(C'153,178,215');
//--- Die Erzeugung des Buttons
   if(!m_simple_button3.CreateSimpleButton(m_chart_id,m_subwin,button_text,x,y))
      return(false);
//--- Hinzufügen des Objektes zu dem gemeinsamen Array von Objektgruppen
   CWndContainer::AddToElementsArray(0,m_simple_button3);
   return(true);
  }

Alle Methoden für die Erzeugung des Elementes werden in der CProgram::CreateTradePanel() Methode aufgerufen:

//+-----------------------------------------------------------------+
//| Erzeugung des Trading-Panels                                    |
//+-----------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Erzeugung einer Form für Controls
//---Erzeugung der Controls:
//    Hauptmenü
//--- Kontextmenüs
//--- Einfache buttons
   if(!CreateSimpleButton1("Simple Button 1"))
      return(false);
   if(!CreateSimpleButton2("Simple Button 2"))
      return(false);
   if(!CreateSimpleButton3("Simple Button 3"))
      return(false);
//--- Neuzeichnen auf dem Chart
   m_chart.Redraw();
   return(true);
  }

Die Nachricht mit dem ON_CLICK_BUTTON Event identifizierer wird in dem CProgram::OnEvent() Eventhandler empfangen. Als Beispiel implementieren wir den Programmcode, in welchem der Name des Buttons überprüft wird. Wenn sich herausstellt, dass der dritte Button angeklickt wurde, dann definiert sein aktueller Zustand den Zustand des zweiten Buttons. Das heißt, dass wenn der dritte Button gedrückt wurde, der Zweite blockiert wird und umgekehrt.

//+-----------------------------------------------------------------+
//| Event handler                                                   |
//+-----------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Verarbeiten des Button-Events
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      ::Print("id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
      //---
      if(sparam==m_simple_button3.Text())
        {
         if(m_simple_button3.IsPressed())
            m_simple_button1.ButtonState(false);
         else
            m_simple_button1.ButtonState(true);
        }
     }
  }

Der nachfolgende Screenshot zeigt das Ergebnis der Kompilierung und dem Hinzufügen des Programms zu dem Chart. In dem linken Screenshot ist der Simple Button 3 Button nicht gedrückt und der Simple Button 1 Button ist verfügbar. In dem rechten Screenshot ist in der Simple Button 3 gedrückt und der Simple Button 1 ist blockiert. 

 Abbildung  2. Test über das Hinzufügen des Buttons zu dem Chart. Buttons in unterschiedlichen Zuständen.

Abbildung 2. Test über das Hinzufügen des Buttons zu dem Chart. Buttons in unterschiedlichen Zuständen. 

 

In der aktuellen Implementation fehlt noch eine kleine Änderung, welche die Interaktion mit dem Button realistischer erscheinen lässt. Wir müssen noch den Butten die Farbe wechseln lassen, direkt nachdem er angeklickt wurde. Eine Überprüfung ob die linke Maustaste gedrückt worden ist kann über den CHARTEVENT_MOUSE_MOVE Mauszeiger Bewegungs-Event durchgeführt werden. Dazu fügen wir den entsprechenden Code in den CSimpleButton::OnEvent() Eventhandler ein. 

In den Fällen wenn (1) das Element versteckt ist, (2) die Form blockiert ist, (3) die linke Maustaste nicht gedrückt ist (4) und der Button blockiert ist, wird der Eventhandler verlassen. Wenn diese Überprüfungen erfolgreich abgeschlossen worden sind, dann wird die entsprechende Farbe des Buttons in Abhängigkeit von seinem Fokus und dem aktuellen Status geändert.

//+-----------------------------------------------------------------+
//| Event handling                                                  |
//+-----------------------------------------------------------------+
void CSimpleButton::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Verarbeiten des Mauszeiger Bewegungs Events
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Abbrechen, falls das Element versteckt ist
      if(!CElement::IsVisible())
         return;
      //--- Identifizierung des Focus
      int x=(int)lparam;
      int y=(int)dparam;
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      //--- Abbrechen, falls die Forum blockiert ist
      if(m_wnd.IsLocked())
         return;
      //--- Abbrechen, falls die Maustaste nicht gedrückt ist
      if(sparam=="0")
         return;
      //--- Abbrechen, falls der Button blockiert ist
      if(!m_button_state)
         return;
      //--- Falls es keinen Fokus gibt
      if(!CElement::MouseFocus())
        {
         //--- Falls der Button nicht geklickt ist
         if(!m_button.State())
            {
             m_button.Color(m_text_color);
             m_button.BackColor(m_back_color);
            }
         //---
         return;
        }
      //--- Falls er den Fokus besitzt
      else
        {
         m_button.Color(m_text_color_pressed);
         m_button.BackColor(m_back_color_pressed);
         return;
        }
      //---
      return;
     }
  }

Nun sollte alles entsprechend funktionieren. Die Entwicklung der Klasse für die Erzeugung eines einfachen Buttons ist fertig. Sie können den zugehörigen Programmcode in den an diesen Artikel angehängten Dateien finden. Nun betrachten wir eine Klasse für einen Button mit erweiterter Funktionalität.

 


Entwicklung einer Klasse für die Erzeugung eines Icon-Buttons

Ein Icon-Button wird aus drei einfachen grafischen Objekten zusammengestellt:

  1. Hintergrund
  2. Icon.
  3. Text label.

Abbildung 3. Zusammengehörige Teile eines Icon-Buttons-Controls

Abbildung 3. Zusammengehörige Teile eines Icon-Buttons-Controls

 

Für die freie Positionierung des Textes wird ein Text-Label benötigt. Zum Beispiel könnte ein Button so zusammengestellt sein, dass sich einen Text im unteren Teil befindet und darüber ein Icon, oder auch umgekehrt. Wir werden dieses weiter unten in dem Artikel besprechen

Die Klasse für die Erzeugung dieses Controls wird die gleichen Felder und Methoden beinhalten, wie in der Klasse für den CSimpleButton. Neben den Eigenschaften, die sich mit der Größe und der Farbe des Buttons beschäftigen, benötigen wir noch Felder und Methoden für die Ränder des Icons und des Text-Labels in Relation zu den Control-Koordinaten, sowie die Label-Icons in dem aktiven und in dem blockierten Zustand. Wir sind zudem noch eine Option hinzu einen Button nur mit einem Icon zu erzeugen. Ein solcher Button beinhaltet nur ein Objekt vom Typ OBJ_BITMAP_LABEL.

Anschließend erzeugen wir die IconButton.mqh Datei und in ihr die CIconButton Klasse. Beziehen Sie diese in der WndContainer.mqh Datei mit ein. Der nachfolgende Programmcode zeigt nur die Felder und Methoden der CIconButton Klasse, die sie von der Klasse des CSimpleButton unterscheidet.

class CIconButton : public CElement
  {
private:
   //--- Objekte für die Erzeugung eines Buttons
   CButton           m_button;
   CBmpLabel         m_icon;
   CLabel            m_label;
   //--- Die Eigenschaften des Buttons:
   //    Die Icons für den Button in dem aktiven und inaktiven Zustand
   string            m_icon_file_on;
   string            m_icon_file_off;
   //--- Icon Ränder
   int               m_icon_x_gap;
   int               m_icon_y_gap;
   //--- Text und Ränder des Text-Label
   string            m_label_text;
   int               m_label_x_gap;
   int               m_label_y_gap;
   //--- Der Icon-Only-Mode, falls der Button nur aus einem BmpLabel Objekt besteht
   bool              m_only_icon;
   //---
public:
   //--- Methoden für die Erzeugung eines Buttons
   bool              CreateIconButton(const long chart_id,const int subwin,const string button_text,const int x,const int y);
   //---
private:
   bool              CreateButton(void);
   bool              CreateIcon(void);
   bool              CreateLabel(void);
   //---
public:
   //--- Festlegung des Icon only Modus
   void              OnlyIcon(const bool flag)                { m_only_icon=flag;               }
   //--- Festlegung der Icons des Buttons in dem aktiven und dem inaktiven Status
   void              IconFileOn(const string file_path)       { m_icon_file_on=file_path;       }
   void              IconFileOff(const string file_path)      { m_icon_file_off=file_path;      }
   //--- Icon Ränder
   void              IconXGap(const int x_gap)                { m_icon_x_gap=x_gap;             }
   void              IconYGap(const int y_gap)                { m_icon_y_gap=y_gap;             }
   //--- Die Ränder des Text Labels
   void              LabelXGap(const int x_gap)               { m_label_x_gap=x_gap;            }
   void              LabelYGap(const int y_gap)               { m_label_y_gap=y_gap;            }
  };

Alle Felder der Klasse müssen mit Standardwerten initialisiert werden:

//+-----------------------------------------------------------------+
//| Konstruktor                                                     |
//+-----------------------------------------------------------------+
CIconButton::CIconButton(void) : m_icon_x_gap(4),
                                 m_icon_y_gap(3),
                                 m_label_x_gap(25),
                                 m_label_y_gap(4),
                                 m_icon_file_on(""),
                                 m_icon_file_off(""),
                                 m_button_state(true),
                                 m_two_state(false),
                                 m_only_icon(false),
                                 m_button_y_size(18),
                                 m_back_color(clrLightGray),
                                 m_back_color_off(clrLightGray),
                                 m_back_color_hover(clrSilver),
                                 m_back_color_pressed(clrBlack),
                                 m_border_color(clrWhite),
                                 m_border_color_off(clrDarkGray),
                                 m_label_color(clrBlack),
                                 m_label_color_off(clrDarkGray),
                                 m_label_color_hover(clrBlack),
                                 m_label_color_pressed(clrBlack)
  {
//--- Abspeichern des namens der Elementklasse in der Basisklasse  
   CElement::ClassName(CLASS_NAME);
//--- Festlegung der Prioritäten für die linke Maustaste
   m_button_zorder =1;
   m_zorder        =0;
  }

Die Methode für die Erzeugung aller Objekte des Buttons, enthält eine Überprüfung ob der Icon-Only-Modus existiert. Wenn sich herausstellt, dass der Button nur aus einem icon erzeugt werden soll, dann verlässt das Programm ganz am Anfang die Methode um die Erzeugung des Hintergrundes und das Text Label zu veranlassen.

//+-----------------------------------------------------------------+
//| Erzeugte den Hintergrund des Buttons                            |
//+-----------------------------------------------------------------+
bool CIconButton::CreateButton(void)
  {
//--- Verlassen, falls der Icon only Mode aktiviert ist
   if(m_only_icon)
      return(true);
//--- ... etc.
  }
//+-----------------------------------------------------------------+
//| Erzeugung des Textes für den Button                             |
//+-----------------------------------------------------------------+
bool CIconButton::CreateLabel(void)
  {
//--- Verlassen, falls der Icon only Mode aktiviert ist
   if(m_only_icon)
      return(true);
//--- ... etc.
  }

Falls ein Button nur aus einem Icon erzeugt werden soll, dann wird die Methode einen weiteren Check für die Überprüfung des Vorliegens von Icons durchführen. Falls der User kein Icon definiert hat, dann wird die Erzeugung des grafischen Interfaces an dieser Stelle abgebrochen und in dem Journal wird eine Nachricht eingeblendet, dass die Icons in diesem Modus verpflichtend sind. The CIconButton::CreateIcon() method for creating a button icon is presented in the code below:

//+-----------------------------------------------------------------+
//| Erzeugt ein Button Icon                                         |
//+-----------------------------------------------------------------+
bool CIconButton::CreateIcon(void)
  {
//--- Falls der Icon-Only-Modus deaktiviert ist
   if(!m_only_icon)
     {
      //--- Verlassen, falls das Icon für den Button nicht benötigt wird
      if(m_icon_file_on=="" || m_icon_file_off=="")
         return(true);
     }
//--- Falls der Icon-Only-Modus aktiviert ist 
   else
     {
      //--- Falls kein icon definiert worden ist, Ausgeben einer Nachricht und verlassen
      if(m_icon_file_on=="" || m_icon_file_off=="")
        {
         ::Print(__FUNCTION__," > The icon must be defined in the \"Icon only\" mode.");
         return(false);
        }
     }
//--- Erstellen des Objektnamens
   string name=CElement::ProgramName()+"_icon_button_bmp_"+(string)CElement::Id();
//--- Koordinaten
   int x =(!m_only_icon)? m_x+m_icon_x_gap : m_x;
   int y =(!m_only_icon)? m_y+m_icon_y_gap : m_y;
//--- Festlegen des Icons
   if(!m_icon.Create(m_chart_id,name,m_subwin,x,y))
      return(false);
//--- Festlegen der Eigenschaften
   m_icon.BmpFileOn("::"+m_icon_file_on);
   m_icon.BmpFileOff("::"+m_icon_file_off);
   m_icon.State(true);
   m_icon.Corner(m_corner);
   m_icon.GetInteger(OBJPROP_ANCHOR,m_anchor);
   m_icon.Selectable(false);
   m_icon.Z_Order((!m_only_icon)? m_zorder : m_button_zorder);
   m_icon.Tooltip((!m_only_icon)? "\n" : m_label_text);
//--- Abspeichern der Koordinaten
   m_icon.X(x);
   m_icon.Y(y);
//--- Abspeichern der Größe
   m_icon.XSize(m_icon.X_Size());
   m_icon.YSize(m_icon.Y_Size());
//--- Ränder von den Kanten
   m_icon.XGap(x-m_wnd.X());
   m_icon.YGap(y-m_wnd.Y());
//--- Abspeichern des Objekt-Pointers
   CElement::AddToArray(m_icon);
   return(true);
  }

Die CIconButton besitzt keine weiteren Unterschiede zu der CSimpleButton Klasse. Sie finden die vollständigen Versionen in den Dateien die diesen Artikel beigefügt sind.

Jetzt werden wir die Buttons vom Typ CIconButton testen. Alle Möglichkeiten demonstrieren zu können, erzeugen wir in dem Test EA fünf dieser Buttons mit unterschiedlichen Modi, Größen und Zuständen. Wir erzeugung fünf Instanzen der CIconButton Klasse in der benutzerdefinierten Klasse des Test EAs und deklarieren fünf Methoden für die Erzeugung von Buttons, wie es der nachfolgende Programmcode zeigt. 

class CProgram : public CWndEvents
  {
private:
   //--- Icon Buttons
   CIconButton       m_icon_button1;
   CIconButton       m_icon_button2;
   CIconButton       m_icon_button3;
   CIconButton       m_icon_button4;
   CIconButton       m_icon_button5;
   //---
private:
   //--- Icon Buttons
#define ICONBUTTON1_GAP_X        (7)
#define ICONBUTTON1_GAP_Y        (105)
   bool              CreateIconButton1(const string text);
#define ICONBUTTON2_GAP_X        (128)
#define ICONBUTTON2_GAP_Y        (105)
   bool              CreateIconButton2(const string text);
#define ICONBUTTON3_GAP_X        (7)
#define ICONBUTTON3_GAP_Y        (130)
   bool              CreateIconButton3(const string text);
#define ICONBUTTON4_GAP_X        (88)
#define ICONBUTTON4_GAP_Y        (130)
   bool              CreateIconButton4(const string text);
#define ICONBUTTON5_GAP_X        (169)
#define ICONBUTTON5_GAP_Y        (130)
   bool              CreateIconButton5(const string text);
  };

Wir verwenden hier nur die Implementation von einer dieser Methoden zeigen, da alle gleich aussehen. Sie haben lediglich unterschiedliche Werte von Parametern:

//+-----------------------------------------------------------------+
//| Erzeugung des icon button 5                                     |
//+-----------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp64\\gold.bmp"
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp64\\gold_colorless.bmp"
//---
bool CProgram::CreateIconButton5(const string button_text)
  {
//--- Übergabe des Panel Objektes
   m_icon_button5.WindowPointer(m_window);
//--- Koordinaten
   int x=m_window.X()+ICONBUTTON5_GAP_X;
   int y=m_window.Y()+ICONBUTTON5_GAP_Y;
//--- Festlegen der Eigenschaften bevor er erzeugt wird
   m_icon_button5.ButtonXSize(76);
   m_icon_button5.ButtonYSize(87);
   m_icon_button5.LabelXGap(6);
   m_icon_button5.LabelYGap(69);
   m_icon_button5.LabelColor(clrBlack);
   m_icon_button5.LabelColorPressed(clrBlack);
   m_icon_button5.BackColor(clrGainsboro);
   m_icon_button5.BackColorHover(C'193,218,255');
   m_icon_button5.BackColorPressed(C'210,210,220');
   m_icon_button5.BorderColor(C'150,170,180');
   m_icon_button5.BorderColorOff(C'178,195,207');
   m_icon_button5.IconXGap(6);
   m_icon_button5.IconYGap(3);
   m_icon_button5.IconFileOn("Images\\EasyAndFastGUI\\Icons\\bmp64\\gold.bmp");
   m_icon_button5.IconFileOff("Images\\EasyAndFastGUI\\Icons\\bmp64\\gold_colorless.bmp");
//--- Erzeugung des Controls
   if(!m_icon_button5.CreateIconButton(m_chart_id,m_subwin,button_text,x,y))
      return(false);
//--- Hinzufügen des Pointers von dem Control zu der Basisklasse
   CWndContainer::AddToElementsArray(0,m_icon_button5);
   return(true);
  }

 Plazieren Sie die Aufrufe dieser Methoden in der Hauptmethode für die Erzeugung von grafischen Interfaces. 

Dem Eventhandler die Nachverfolgung des Klicks auf den Icon Button 2 hinzufügen. In der EA Version, welche diesem Artikel beigefügt ist, arbeitet der Button in zwei Modi (gedrückt / nicht gedrückt). Die Verfügbarkeit von Icon Button 1 und Icon Button 4 hängt von dem Status des Icon Button 2 ab.. Falls dieser Button nicht gedrückt ist, dann sind alle von ihm abhängige Buttons blockiert und umgekehrt. 

//+-----------------------------------------------------------------+
 //| Event handler                                                  |
//+-----------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Verarbeiten des Button-Events
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      Print("id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
      //---
      if(sparam==m_simple_button3.Text())
        {
         if(m_simple_button3.IsPressed())
            m_simple_button1.ButtonState(false);
         else
            m_simple_button1.ButtonState(true);
        }
      //---
      if(sparam==m_icon_button2.Text())
        {
         if(m_icon_button2.IsPressed())
           {
            m_icon_button1.ButtonState(true);
            m_icon_button4.ButtonState(true);
           }
         else
           {
            m_icon_button1.ButtonState(false);
            m_icon_button4.ButtonState(false);
           }
        }
     }
  }

Nachdem sie den Programmcode kompiliert haben und den Test EA dem Chart hinzugefügt haben, sollten Sie das folgende Ergebnis sehen:

Abbildung 4. Test des Icon Button Controls.

Abbildung 4. Test des Icon Button Controls.

 

Wir haben die Entwicklung der CIconButton Klasse für die Erzeugung eines Buttons mit erweiterter Funktionalität fertiggestellt. Die Icons, die in den Buttons dargestellt werden, können sie am Ende dieses Artikels herunterladen. Nun werden wir die Klasse für die Erzeugung eines Split Buttons besprechen. 


Entwicklung einer Klasse für die Erzeugung eines Split-Buttons

Was ist ein Split Button? Ein splitbutton besteht aus zwei Komponenten:

  • Die erste Komponente besteht auf einen Button mit einer eingebauten Hauptfunktion.
  • Die zweite Komponente besteht aus einem Button, welcher ein Kontextmenü mit zusätzlichen Funktionen hervorbringt.

Ein solches Control wird sehr häufig in grafischen Interfaces verwendet. Solche Buttons werden verwendet, wenn mehrere Funktionen dicht gruppiert werden müssen, und alle von Ihnen zur gleichen Kategorie gehören. 

Ein Split Button wird aus 5 Objekten zusammengestellt und einem hinzufügbaren Element (Context-Menü):

  1. Hintergrund
  2. Icon.
  3. Text.
  4. Hintergrund des zusätzlichen Buttons.
  5. Drop-down Menü Indikator.

 

Abbildung 5. Bestandteile des Split Button Controls.

Abbildung 5. Bestandteile des Split Button Controls.

 

Wir sehen hier, dass das Kontextmenü (Ein Objekt der CContextMenu Klasse) Dem Objekt der CMenuItem Klasse nicht hinzugefügt werden kann. Das bedeutet, dass wir einen zusätzlichen Modus benötigen, wenn wir ein Kontextmenü zu irgendeinem anderen Objekt hinzufügen oder wieder davon entfernen wollen. 

Zudem benötigen wir einen Bezugspunkt für die Einstellungen über die Interaktion zwischen diesen controls, welche durch den User temporär aktiviert werden können (Drop-Down-Elemente) Es muss so eingerichtet werden, dass eine Form nur von dem Element freigegeben werden kann welche sie auch gesperrt hat. Wenn wir dieses nicht machen, dann können Konflikte zwischen den einzelnen Elementen entstehen, da der Status von der Form auch den Status von einigen Elementen definiern kann. Wir werden dieses später an einem Beispiel veranschaulichen. Bei einem Test kann man besser darstellen, wie solche Konflikte entstehen können. Um diese Funktionalität implementieren zu können, müssen wir noch ein Feld und eine Methode für das Speichern und erhalten von aktivierten Elementen der CWindow Klasse hinzufügen:

class CWindow : public CElement
  {
private:
   //--- Identifizierer des aktivierten zum Controls
   int               m_id_activated_element;
   //---
public:
   //--- Methoden für das Speichern und das Erhalten der ID des aktivierten Elementes
   int               IdActivatedElement(void)                          const { return(m_id_activated_element);     }
   void              IdActivatedElement(const int id)                        { m_id_activated_element=id;          }
  };

Wenn während der Aktivierung des Elementes die Form geblockt werden soll, dann muss der Identifizierer dieses Elementes in der Form abgespeichert werden An der Stelle wo die Form wieder freigegeben werden soll, muss eine Überprüfung stattfinden, wie der Identifizierer des Elementes lautet, welcher die Form gesperrt hat. Eine Überprüfung der Identifizierung des Elementes, welches die Form gesperrt hat, ist nicht notwendig, da eine Überprüfung des Namens des Objektes stattgefunden hat. 

Anschließend erweitern wir die CContextMenu Klasse. Sie wird die Aktivierung und Handhabung des abgelösten Kontextmenü-Modus ermöglichen, wie es im Folgenden Programmcode gezeigt wird: Der Modus des Kontextmenüs mit der Bindung zu dem vorherigen Knoten wird standardmäßig gesetzt: 

class CContextMenu : public CElement
  {
   //--- Der freistehende Kontextmenü-Modus.. Das bedeutet, dass es keine Bindung zu einem vorherigen Knotenpunkt gibt.
   bool              m_free_context_menu;
   //---
public:
   //--- Festlegen des freistehenden Kontextmenü-Modus
   void              FreeContextMenu(const bool flag)               { m_free_context_menu=flag;             }
  };
//+-----------------------------------------------------------------+
//| Konstruktor                                                     |
//+-----------------------------------------------------------------+
CContextMenu::CContextMenu(void) : m_free_context_menu(false)
  {
  }

Die internen Identifizierer für die Verwaltung eines Klicks auf den Menüpunkt unterscheiden sich je nachdem ob es sich um ein verbundenes oder ein freistehendes Kontextmenü handelt. Eine solche Aufteilung macht den Programmcode eindeutiger, minimiert die Anzahl der Bedingungen und erlaubt eine flexiblere Verwaltung der Events der Elemente.

Hinzufügen eines neuen Identifizierers (ON_CLICK_FREEMENU_ITEM) für die Erzeugung eines Events von einem freistehenden Kontextmenü zu der Defines.mqh Datei:

#define ON_CLICK_FREEMENU_ITEM    (9)  // Clicking on the item of a detached context menu

Für die Überprüfung des Modus des freistehenden Kontextmenüs, müssen noch zusätzliche Bedingungen in der CContextMenu Klasse hinzugefügt werden. Nachfolgend finden Sie die abgekürzten Versionen der Methoden. Die Kommentare dienen der Orientierung.

1. In dem Eventhandler:

//+-----------------------------------------------------------------+
//| Event handler                                                   |
//+-----------------------------------------------------------------+
void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Verwaltung der Bewegung des Mauszeigers
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Abbrechen, falls das Element versteckt ist
      //--- Erhalten des Focus
      //--- Verlassen, falls es sich um ein freistehendes Kontextmenü handelt
      if(m_free_context_menu)
         return;
      //--- Falls das Kontextmenü aktiviert ist und die linke Maustaste betätigt wurde
      //--- Überprüfen der Bedingungen für das Schließen aller Kontextmenüs, welche unterhalb dieses Elements geöffnet sind.
      return;
     }
//--- Verarbeitendes Events bei einem Klick mit der linken Maustaste
//--- Verarbeiten des ON_CLICK_MENU_ITEM Events
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_MENU_ITEM)
     {
      //--- Verlassen, falls es sich um ein freistehendes Kontextmenü handelt
      if(m_free_context_menu)
         return;
      //--- Empfangen der Nachricht von dem Menü Item für die Verarbeitung
      return;
     }
  }

2. In der Methode für die Erzeugung eines Kontextmenüs:

//+-----------------------------------------------------------------+
//| Erzeugt ein Kontextmenü                                         |
//+-----------------------------------------------------------------+
bool CContextMenu::CreateContextMenu(const long chart_id,const int subwin,const int x=0,const int y=0)
  {
//--- Verlassen, wenn es keinen Pointer zu einer Form gibt
//--- Falls es sich um ein angefügtes Kontextmenü handelt
   if(!m_free_context_menu)
     {
      //--- Verlassen, falls es keinen Pointer zu einem vorherigen Knotenpunkt gibt 
      if(::CheckPointer(m_prev_node)==POINTER_INVALID)
        {
         ::Print(__FUNCTION__," > Before creating a context menu it must be passed "
                 "the pointer to the previous node using the CContextMenu::PrevNodePointer(CMenuItem &object) method.");
         return(false);
        }
     }
//--- Initialisierung der Variablen
//--- Wenn die Koordinaten nicht spezifiziert wurden
//--- Falls die Koordinaten spezifiziert wurden
//--- Ränder von den Kanten
//--- Erzeugen eines Kontextmenüs
//--- Verstecken des Elementes
   return(true);
  }

3. In der Methode für die Erzeugung einer Liste von Menu Items:

//+-----------------------------------------------------------------+
//| Erzeugen einer Liste von Menü-Items                             |
//+-----------------------------------------------------------------+
bool CContextMenu::CreateItems(void)
  {
   int s =0;     // For identifying the location of separation lines
   int x =m_x+1; // X coordinate
   int y =m_y+1; // Y coordinate. Will be calculated in a loop for every menu item.
//--- Anzahl von Abstandslinien
//---
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Berechnung der Y Koordinate
      //--- Abspeichern des Pointers der Form
      //--- Falls das Kontextmenü einen Anhang besitzt, wird der Pointer zu dem vorherigen Knotenpunkt hinzugefügt
      if(!m_free_context_menu)
         m_items[i].PrevNodePointer(m_prev_node);
      //--- Festlegen der Eigenschaften
      //--- Ränder von den Kanten des Panels
      //--- Erzeugung eines Menü-Items
      //--- Mit dem nächsten fortfahren, wenn alle Trennlinien gesetzt wurden
      //--- Wenn die Indizes übereinstimmen, dann wird eine Trennlinie nach dem Menüpunkt gesetz
     }
   return(true);
  }

4. In der Methode für das Anzeigen und Verstecken eines Kontextmenüs: 

//+-----------------------------------------------------------------+
//| Anzeigen eines Kontextmenüs                                     |
//+-----------------------------------------------------------------+
void CContextMenu::Show(void)
  {
//--- Verlassen, falls das Control schon sichtbar ist
//--- Anzeigen der Objekte des Kontextmenüs
//--- Anzeigen der Menüpunkt
//--- Den Status eines sichtbaren Controls zuweisen
//--- Status des Kontextmenüs
//--- Registrieren des Status in dem vorherigen Knoten.
   if(!m_free_context_menu)
      m_prev_node.ContextMenuState(true);
//--- Blockieren der Form
  }
//+-----------------------------------------------------------------+
//| Versteckt ein Kontext-Menü                                      |
//+-----------------------------------------------------------------+
void CContextMenu::Hide(void)
  {
//--- Abbrechen, falls das Element versteckt ist
//--- Verstecken der Objekte des Kontextmenüs
//--- Verstecken der Menüpunkte
//--- Den Fokus löschen
//--- Den Status eines versteckten Elementes zuweisen
//--- Status des Kontextmenüs
//--- Registrieren des Status in dem vorherigen Knoten.
   if(!m_free_context_menu)
      m_prev_node.ContextMenuState(false);
  }

5. In der Methode für die Verwaltung eines Klicks auf einen Menüpunkt. In dem nächsten Block wird innerhalb einer Schleife eine Überprüfung des Namens des angeklickten Objektes durchgeführt. Wenn ein solches Objekt gefunden wird, dann wird ein Event mit dem ON_CLICK_FREEMENU_ITEM Bezeichner gesendet. Später muss dieses Event von allen Controls verfolgt werden, welche ein Kontextmenü beinhalten (Dieses wird später in einem Beispiel dargestellt). 

//+-----------------------------------------------------------------+
//| Verarbeiten eines Klicks auf einen Menüpunkt                    |
//+-----------------------------------------------------------------+
bool CContextMenu::OnClickMenuItem(const string clicked_object)
  {
//--- Abbrechen, Falls dieses Kontextmenü einen vorherigen Knoten besitzt und schon geöffnet ist.
   if(!m_free_context_menu && m_context_menu_state)
      return(true);
//--- Abbrechen, falls der Klick nicht auf diesem Menüpunkt stattgefunden hat
   if(::StringFind(clicked_object,CElement::ProgramName()+"_menuitem_",0)<0)
      return(false);
//--- Abfragen des Bezeichners und des Indexes über den Objektnamen
   int id    =IdFromObjectName(clicked_object);
   int index =IndexFromObjectName(clicked_object);
//--- Falls das Kontext-Menü vorherige Knoten besitzt
   if(!m_free_context_menu)
     {
      //--- Abbrechen, falls der Klick nicht auf ein Item stattgefunden hat, das zu diesem Kontextmenü gehört
      if(id!=m_prev_node.Id() || index!=m_prev_node.Index())
         return(false);
      //--- Anzeigen des Kontextmenüs
      Show();
     }
//--- Falls es sich um ein freistehendes Kontext-Menü handelt
   else
     {
      //--- In einer Schleife herausfinden welcher Menüpunkt gedrückt wurde
      int total=ItemsTotal();
      for(int i=0; i<total; i++)
        {
         if(m_items[i].Object(0).Name()!=clicked_object)
            continue;
         //--- Eine Nachricht darüber senden
         ::EventChartCustom(m_chart_id,ON_CLICK_FREEMENU_ITEM,CElement::Id(),i,DescriptionByIndex(i));
         break;
        }
     }
//---
   return(true);
  }

Nun ist alles bereit um eine Klasse mit einem Split Button zu entwickeln Erzeugen sie eine SplitButton.mqh Datei mit der CSplitButton Klasse und Standardmethoden für alle Controls in dem Controls Verzeichnis:

//+------------------------------------------------------------------+
//|                                                  SplitButton.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
#include "ContextMenu.mqh"
//+------------------------------------------------------------------+
//| Klasse für die Erzeugung eines Split buttons                     |
//+------------------------------------------------------------------+
class CSplitButton : public CElement
  {
private:
   //--- Ein Pointer zu der Form zu welchem das Element hinzugefügt worden ist
   CWindow          *m_wnd;
   //---
public:
                     CSplitButton();
                    ~CSplitButton();

   //--- Speichert den Pointer
   void              WindowPointer(CWindow &object)           { m_wnd=::GetPointer(object);         }

   //--- Chart Eventhandler
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Timer
   virtual void      OnEventTimer(void);
   //--- Bewegen des Elementes
   virtual void      Moving(const int x,const int y);
   //--- (1) Anzeigen, (2) verstecken, (3) zurücksetzen, (4) löschen
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) Setzen, (2) Zurücksetzen der linken Maustaste
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
  };

Zusätzlich zu den Eigenschaften und Methoden der Charakteristiken für Buttons aller Typen, die wir bereits in diesem Artikel beschrieben haben, brauchen wir jetzt zusätzlich noch welche für das erstellen von Buttons mit einem Dropdown Menüs:

  • Größe. In dieser Version verwenden wir nur die Breite. Die Höhe wird der Höhe des Haupt-Buttons entsprechen
  • Die Priorität für einen Klick mit der linken Maustaste Ein Button mit einem Dropdown-Menü benötigt einen höheren Prioritätlevel, als ein einfacher Button.
  • Icons und Ränder für das Icon in dem Button.
  • Status des Kontextmenüs in des Buttons (sichtbar/versteckt). 

class CSplitButton : public CElement
  {
private:
   //--- Größe und Priorität eines Klicks mit der linken Maustaste für einen Button mit Dropdown
   int               m_drop_button_x_size;
   int               m_drop_button_zorder;
   //--- Icon Ränder
   int               m_drop_arrow_x_gap;
   int               m_drop_arrow_y_gap;
   //--- Icons eines Buttons mit einem Drop-Down Menü im aktivierten und blockierten Zustand
   string            m_drop_arrow_file_on;
   string            m_drop_arrow_file_off;
   //--- Status des Kontextmenüs 
   bool              m_drop_menu_state;
   //---
public:
   //--- Die Größe eines Buttons mit einem Dropdown Menü
   void              DropButtonXSize(const int x_size)        { m_drop_button_x_size=x_size;        }
   //--- Festlegung der Icons für einen Button mit einem Dropdown Menü im aktivierten und gesperrten Status
   void              DropArrowFileOn(const string file_path)  { m_drop_arrow_file_on=file_path;     }
   void              DropArrowFileOff(const string file_path) { m_drop_arrow_file_off=file_path;    }
   //--- Icon Ränder
   void              DropArrowXGap(const int x_gap)           { m_drop_arrow_x_gap=x_gap;           }
   void              DropArrowYGap(const int y_gap)           { m_drop_arrow_y_gap=y_gap;           }
  };

Wie schon zuvor erklärt, benötigt die Erzeugung eines Split-Buttons fünf einfache Objekte und ein Kontextmenü. Lassen Sie die Instanzen der benötigen Klassen und Methoden für die Erzeugung deklarieren. Wir benötigen zu den Methoden für die Gestaltung des Kontextmenüs (hinzufügen von Menüpunkten und Trennlinien). Da die Eigenschaften des Kontextmenüs durch den Anwender festgelegt werden, wird eine Methode für das Erhalten eines Pointers zu dem Button mit dem Kontextmenü notwendig.

class CSplitButton : public CElement
  {
private:
   //--- Objekte für die Erzeugung eines Buttons
   CButton           m_button;
   CBmpLabel         m_icon;
   CLabel            m_label;
   CEdit             m_drop_button;
   CBmpLabel         m_drop_arrow;
   CContextMenu      m_drop_menu;
   //---
public:
   //--- Methoden für die Erzeugung eines Buttons
   bool              CreateSplitButton(const long chart_id,const string button_text,const int window,const int x,const int y);
   //---
private:
   bool              CreateButton(void);
   bool              CreateIcon(void);
   bool              CreateLabel(void);
   bool              CreateDropButton(void);
   bool              CreateDropIcon(void);
   bool              CreateDropMenu(void);
   //---
public:
   //--- Erhalt des Pointers von dem Kontextmenü,
   CContextMenu     *GetContextMenuPointer(void)        const { return(::GetPointer(m_drop_menu));  }
   //--- Fügt einen Menüpunkt mit den angegebenen Eigenschaften vor der Erzeugung des Kontextmenüs hinzu
   void              AddItem(const string text,const string path_bmp_on,const string path_bmp_off);
   //--- Fügt eine Trennlinie nach dem spezifizierten Menüpunkt vor der Erzeugung des Kontextmenüs hinzu.
   void              AddSeparateLine(const int item_index);
  };

Die Methoden für das Erzeugen der Objekte haben keine grundlegenden Unterschiede zu denen die wir bereits besprochen haben. Der einzige Unterschied besteht darin, dass in der Methode für die Erzeugung des Kontextmenüs der Bezeichner des Split Buttons als Teil des Controls mit angegeben werden muss. Dieses ermöglicht es uns später zu ermitteln, von welchen Bezeichner der ON_CLICK_FREEMENU_ITEM ausgelöst wurde.

//+-----------------------------------------------------------------+
//| Erzeugt ein drop-down Menü                                      |
//+-----------------------------------------------------------------+
bool CSplitButton::CreateDropMenu(void)
  {
//--- Übergabe des Panel Objektes
   m_drop_menu.WindowPointer(m_wnd);
//--- Ein ungebundenes Kontextmenü
   m_drop_menu.FreeContextMenu(true);
//--- Koordinaten
   int x=m_x;
   int y=m_y+m_y_size;
//--- Festlegen der Eigenschaften
   m_drop_menu.Id(CElement::Id());
   m_drop_menu.XSize((m_drop_menu.XSize()>0)? m_drop_menu.XSize() : m_button_x_size);
//--- Einrichten eines Kontextmenüs
   if(!m_drop_menu.CreateContextMenu(m_chart_id,m_subwin,x,y))
      return(false);
//---
   return(true);
  }

Wir betrachten nun die Methoden für die Interaktion mit einem Split button. Da ein Button dieses Typs aus zwei Teilen besteht, benötigen wir zwei separate Methoden für die Handhabung der Klicks auf diese Objekte. These methods will be called in the class handler in the block of the CHARTEVENT_OBJECT_CLICK event

class CSplitButton : public CElement
  {
private:
   //--- Verarbeitendes Button click events
   bool              OnClickButton(const string clicked_object);
   //--- Verarbeiten eines Klicks auf einen Button mit einem Dropdown Menü
   bool              OnClickDropButton(const string clicked_object);
  };
//+-----------------------------------------------------------------+
//| Event handler                                                   |
//+-----------------------------------------------------------------+
void CSplitButton::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Verarbeitendes Events bei einem Klick mit der linken Maustaste
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Klick auf einen einfachen Button
      if(OnClickButton(sparam))
         return;
      //--- Klick auf einen Button mit einem Dropdown Menü
      if(OnClickDropButton(sparam))
         return;
     }
  }

In der CSplitButton::OnClickButton() Methode für die Verwaltung eines Klicks auf einen Haupt Button, wird zunächst der Name des Objektes geprüft. Wenn es sich um den Namen der Klasse dieser Instanz handelt, dann wird der Status des Buttons überprüft. Wenn der Button gesperrt ist, dann bricht das Programm diese Methode ab. Der Hauptmann kann nur einen Status besitzen. Das bedeutet, dass er zunächst zu einem nicht gedrückten Status zurückkehren muss, nachdem er gedrückt wurde. Wenn diese Überprüfungen erfolgreich waren, dann (1) muss das Kontextmenü versteckt werden, falls es sichtbar ist, (2) der entsprechende Status und die Farbe für das Menü und den Button müssen gesetzt werden, (3) Die Form muss entsperrt werden und der Bezeichner des aktivierenden Elementes muss im Speicher zurückgesetzt werden. 

Am Ende der Methode wird eine Nachricht gesendet. Sie kann in der benutzerdefinierten Klasse empfangen werden. Diese Nachricht beinhaltet (1) die ON_CLICK_BUTTON Event Bezeichner, (2) den Element-Bezeichner, (3) den Index des Elementes (4) die dargestellte Beschreibung des Buttons. 

//+-----------------------------------------------------------------+
//| Drücken des Buttons                                             |
//+-----------------------------------------------------------------+
bool CSplitButton::OnClickButton(const string clicked_object)
  {
//--- Abbrechen, falls der Name des Objektes nicht zutrifft  
   if(clicked_object!=m_button.Name())
      return(false);
//--- Abbrechen, falls der Button blockiert ist
   if(!m_button_state)
     {
      //--- Button zurücksetzen
      m_button.State(false);
      return(false);
     }
//--- Verstecken des Menüs
   m_drop_menu.Hide();
   m_drop_menu_state=false;
//--- Zurücksetzen des Buttons und Farbe des Focus setzen
   m_button.State(false);
   m_button.BackColor(m_back_color_hover);
   m_drop_button.BackColor(m_back_color_hover);
//--- Die Form entsperren
   m_wnd.IsLocked(false);
   m_wnd.IdActivatedElement(WRONG_VALUE);
//--- Eine Nachricht darüber senden
   ::EventChartCustom(m_chart_id,ON_CLICK_BUTTON,CElement::Id(),CElement::Index(),m_label.Description());
   return(true);
  }

Die CSplitButton::OnClickDropButton() Methode, welche die den Klick auf einen Button mit einem Dropdown Menü verarbeitet, führt am Anfang noch zwei Überprüfungen durch. Diese sind Überprüfungen über (1) den Namen und (2) Die Verfügbarkeit des Buttons. Anschließend wechselt das Programm zu einen der zwei Blöcke, in Abhängigkeit davon, ob der aktuelle Status sichtbar ist oder nicht.

//+-----------------------------------------------------------------+
//| Drücken eines Buttons mit einem Dropdown Menü                   |
//+-----------------------------------------------------------------+
bool CSplitButton::OnClickDropButton(const string clicked_object)
  {
//--- Abbrechen, falls der Name des Objektes nicht zutrifft  
   if(clicked_object!=m_drop_button.Name())
      return(false);
//--- Abbrechen, falls der Button blockiert ist
   if(!m_button_state)
     {
      //--- Button zurücksetzen
      m_button.State(false);
      return(false);
     }
//--- Falls die Liste angezeigt wird, verstecke sie
   if(m_drop_menu_state)
     {
      m_drop_menu_state=false;
      m_drop_menu.Hide();
      m_button.BackColor(m_back_color_hover);
      m_drop_button.BackColor(m_back_color_hover);
      //--- Entsperre die Form und setze die aktivierende Element ID zurück
      m_wnd.IsLocked(false);
      m_wnd.IdActivatedElement(WRONG_VALUE);
     }
//--- Falls die Liste versteckt ist, dann anzeigen
   else
     {
      m_drop_menu_state=true;
      m_drop_menu.Show();
      m_button.BackColor(m_back_color_hover);
      m_drop_button.BackColor(m_back_color_pressed);
      //--- Sperre die Form und speichere die aktivierende Element ID
      m_wnd.IsLocked(true);
      m_wnd.IdActivatedElement(CElement::Id());
     }
//---
   return(true);
  }

Wir haben bereits die CContextMenu Klasse mit einer Funktion versehen, welche in dem "Free Mode" das Senden von Events mit dem ON_CLICK_FREEMENU_ITEM Bezeichner für die interne Verarbeitung erleichtert. Diese Nachricht wird in dem Eventhandler der CSplitButton Klasse empfangen. Um zu überprüfen, ob diese Nachricht von einem relativen Kontext-Menü stammt, muss der Bezeichner überprüft werden. Dieser ist in dem lparam Parameter enthalten. Wenn der Bezeichner übereinstimmt, (1) Muss das Menü versteckt werden, (2) Die Farben müssen entsprechend des Status gesetzt werden (3) Die Form muss entsperrt werden, falls dieses Element das aktivierende Element war. Anschließend muss eine Nachricht mit dem ON_CLICK_CONTEXTMENU_ITEM Bezeichner gesendet werden.. Diese Nachricht kann in der benutzerdefinierten Klasse empfangen werden

Dafür erzeugen wir eine zusätzliche Methode CSplitButton::HideDropDownMenu(). Der Sinn der Methode ist das Menü zu verstecken, die Form zu entsperren und das Zurücksetzen des Bezeichners des aktivierenden Elementes.

class CSplitButton : public CElement
  {
private:
   //--- Versteckt das Dropdown-Menü
   void              HideDropDownMenu(void);
  };
//+-----------------------------------------------------------------+
//| Event handler                                                   |
//+-----------------------------------------------------------------+
void CSplitButton::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Verarbeitendes Events des Anklickens eines freien Menüpunktes
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_FREEMENU_ITEM)
     {
      //--- Abbrechen, falls der Bezeichner nicht zutrifft
      if(CElement::Id()!=lparam)
         return;
      //--- Verstecken das Dropdown-Menüs
      HideDropDownMenu();
      //--- Senden einer Nachricht
      ::EventChartCustom(m_chart_id,ON_CLICK_CONTEXTMENU_ITEM,lparam,dparam,sparam);
      return;
     }
  }
//+-----------------------------------------------------------------+
//| Versteckt einen Drop down menu                                  |
//+-----------------------------------------------------------------+
void CSplitButton::HideDropDownMenu(void)
  {
//--- Verstecken des Menüs und entsprechende Hinweise 
   m_drop_menu.Hide();
   m_drop_menu_state=false;
   m_button.BackColor(m_back_color);
   m_drop_button.BackColor(m_back_color);
//--- Entsperren der Form, falls der Bezeichner der Form und des Elementes übereinstimmen
   if(m_wnd.IdActivatedElement()==CElement::Id())
     {
      m_wnd.IsLocked(false);
      m_wnd.IdActivatedElement(WRONG_VALUE);
     }
  }

Jetzt müssen wir die Reaktion auf den Split Button in Zusammenhang mit dem Mauszeiger und dem Status der linken Maustaste, wenn sich der Mauszeiger über dem Button befindet programmieren. Dieses benötigt eine weitere Methode, die wir CSplitButton::CheckPressedOverButton() nennen. Diese Methode besitzt lediglich einen Parameter - Den Status der linken Maustaste. Zunächst werden zwei Überprüfungen durchgeführt. Wenn sich herausstellt, dass (1) Sich der Mauszeiger außerhalb des Bereichs des Buttons befindet und (2) Das Formular gesperrt ist, während dieses nicht das Aktivierende Element ist, dann wird hier die Methode verlassen. Wenn die Überprüfungen erfolgreich abgeschlossen worden sind, dann legt das Programm die Farben entsprechend dem Status der linken Maustaste und über welchem Teil des Buttons sich der Mauszeiger befindet, fest.

class CSplitButton : public CElement
  {
private:
   //--- Überprüfung ob bei gedrückter linker Maustaste sich der Mauszeiger über einem Split Button befindet
   void              CheckPressedOverButton(const bool mouse_state);
  };
//+-----------------------------------------------------------------+
//|Prüfen auf gedrückte linke Maustaste über einem Split button     |
//+-----------------------------------------------------------------+
void CSplitButton::CheckPressedOverButton(const bool mouse_state)
  {
//--- Leave, if it is outside of the element area
   if(!CElement::MouseFocus())
      return;
//--- Abbrechen, wenn eine Form gesperrt ist und  die Identifizierer der Form und des Elementes nicht übereinstimme
   if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id())
      return;
//--- Gedrückte Maustaste
   if(mouse_state)
     {
      //--- Im Bereich des Menübuttons
      if(m_drop_button.MouseFocus())
        {
         m_button.BackColor(m_back_color_hover);
         m_drop_button.BackColor(m_back_color_pressed);
        }
      else
        {
         m_button.BackColor(m_back_color_pressed);
         m_drop_button.BackColor(m_back_color_pressed);
        }
     }
//--- Nicht gedrückte Maustaste
   else
     {
      if(m_drop_menu_state)
        {
         m_button.BackColor(m_back_color_hover);
         m_drop_button.BackColor(m_back_color_pressed);
        }
     }
  }

Die CSplitButton::CheckPressedOverButton() Methode wird in dem Eventhandler über das Event für die Bewegung des Mauszeigers aufgerufen. Dem Aufruf dieser Methode gehen mehrere Überprüfungen voraus, wie (1) Ob das Element versteckt ist, (2) Der Focus, (3) Ob das Element verfügbar ist und (4) Sich der Mauszeiger außerhalb des Bereichs des Elementes befindet, Was alles dazu führen kann kann man dass das nene versteckt wird und der eventhändler verlassen wird bevor die Methode CSplitButton::CheckPressedOverButton() aufgerufen wird. 

//+-----------------------------------------------------------------+
//| Event handler                                                   |
//+-----------------------------------------------------------------+
void CSplitButton::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Verarbeiten des Mauszeiger Bewegungs Events
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Abbrechen, falls das Element versteckt ist
      if(!CElement::IsVisible())
         return;
      //--- Identifizierung des Focus
      int x=(int)lparam;
      int y=(int)dparam;
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      m_drop_button.MouseFocus(x>m_drop_button.X() && x<m_drop_button.X2() && 
                               y>m_drop_button.Y() && y<m_drop_button.Y2());
      //--- Abbrechen, falls der Button blockiert ist
      if(!m_button_state)
         return;
      //--- Der Mauszeiger befindet sich außerhalb des Bereichs des Elementes und die Maus Taste ist gedrückt
      if(!CElement::MouseFocus() && sparam=="1")
        {
         //--- Verlassen, falls das Kontextmenü den Fokus besitzt
         if(m_drop_menu.MouseFocus())
            return;
         //--- Verstecken das Dropdown-Menüs
         HideDropDownMenu();
         return;
        }
      //--- Überprüfung ob bei gedrückter linker Maustaste sich der Mauszeiger über einem Split Button befindet
      CheckPressedOverButton(bool((int)sparam));
      return;
     }
  }

Nun ist die Klasse für den speedbutton bereit für einen Test. Dafür muss sie in der Bibliotheksstruktur korrekt eingefügt werden. Dieses muss immer wieder gemacht werden, wenn ein komplexes Control erzeugt wird. In den Fällen der CSimpleButton und CIconButton waren keine Erweiterungen notwendig. In dem Falle des Split-Buttons ist dieses anders, da es neben dem aktuellen Button auch noch ein Kontext-Menü gibt. Der End-Anwender dieser Bibliothek wird nur die letzte Version verwenden und sich nicht mit dem darin enthaltenen Programmcode beschäftigen. Er hat keine Ahnung darüber, wie dieser Programmcode funktioniert. Das Hauptziel eines Entwicklers einer Bibliothek ist, die Verwendung dieser Bibliothek so einfach wie möglich zu gestalten, damit ein Anwender in nur wenigen Schritten ein grafisches Interface erstellen kann.

Beziehen Sie die Datei mit der CSplitButton Klasse in der WndContainer.mqh Datei mit ein: 

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "SplitButton.mqh"

Anschließend deklarieren und implementieren Sie die Methoden für das Hinzufügen eines Pointers zu dem Kontextmenü mit einem Split-Button zu dem privaten Array, welches zuvor schon erzeugt wurde:

//+------------------------------------------------------------------+
//| Klasse für das Abspeichern aller Interface Objekte               |
//+------------------------------------------------------------------+
class CWndContainer
  {
private:
   //--- Speichert die Pointer zu den Split-Button-Elementen in der Basis ab
   bool              AddSplitButtonElements(const int window_index,CElement &object);
  };
//+---------------------------------------------------------------------------+
//| Speichert die Pointer zu den Elementen eines Split-Buttons in der Basis ab|
//+---------------------------------------------------------------------------+
bool CWndContainer::AddSplitButtonElements(const int window_index,CElement &object)
  {
//--- Abbrechen, falls es sich nicht um einen Split-Button handelt
   if(object.ClassName()!="CSplitButton")
      return(false);
//--- Erhalten des Splitbutton pointers
   CSplitButton *sb=::GetPointer(object);
//--- 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 Kontextmenü pointers
   CContextMenu *cm=sb.GetContextMenuPointer();
//--- Abspeichern der Elemente und Objekte in der Basis
   m_wnd[window_index].m_elements[size]=cm;
   AddToObjectsArray(window_index,cm);
//--- 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
      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 des pointers zu allen Menüpunkt-Objekten 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);
  }

Wie Sie sich erinnern, muss der Aufruf der Methoden wie CWndContainer::AddSplitButtonElements() in der CWndContainer::AddToElementsArray() Methode erfolgen.

//+-----------------------------------------------------------------+
//| Fügt den Pointer dem Elementen Array hinzu                      |
//+-----------------------------------------------------------------+
void CWndContainer::AddToElementsArray(const int window_index,CElement &object)
  {
//--- Falls die Basis keine Forms oder Controls enthält
//--- Falls es eine Anfrage für eine nicht existierende Form gibt
//--- Hinzufügen zu dem gemeinsamen Array von Elementen
//--- Hinzufügen von Element-Objekten zu dem gemeinsamen Array von Objekten
//--- Abspeichern der ID von dem letzten Element in allen Forms
//--- Erhöhung des Zählers der Element-Bezeichners
//--- Abspeichern der Pointer zu den Kontextmenü Objekten in der Basis
//--- Abspeichern der Pointer zu den Hauptmenü Objekten in der Basis
//--- Abspeichern der Pointer der splitbutton Objekte in der Basis
   if(AddSplitButtonElements(window_index,object))
      return;
  }

Alles ist bereit um den Split Button zu testen Wie erzeugen 4 solcher Buttons in unserem Test EA Deklarieren Sie Instanzen der CSplitButton Klasse und Methoden für die Erzeugung der Buttons mit Abständen von dem oberen linken Punkt der Form. Platzieren Sie deren Aufruf in der Hauptmethode für das Erzeugen von grafischen Interfaces des Programms.

class CProgram : public CWndEvents
  {
private:
   //--- Split buttons
   CSplitButton      m_split_button1;
   CSplitButton      m_split_button2;
   CSplitButton      m_split_button3;
   CSplitButton      m_split_button4;
   //---
private:
   //--- Split buttons
#define SPLITBUTTON1_GAP_X       (7)
#define SPLITBUTTON1_GAP_Y       (225)
   bool              CreateSplitButton1(const string text);
#define SPLITBUTTON2_GAP_X       (128)
#define SPLITBUTTON2_GAP_Y       (225)
   bool              CreateSplitButton2(const string text);
#define SPLITBUTTON3_GAP_X       (7)
#define SPLITBUTTON3_GAP_Y       (250)
   bool              CreateSplitButton3(const string text);
#define SPLITBUTTON4_GAP_X       (128)
#define SPLITBUTTON4_GAP_Y       (250)
   bool              CreateSplitButton4(const string text);
  };
//+-----------------------------------------------------------------+
//| Erzeugung des Trading-Panels                                    |
//+-----------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Erzeugung einer Form für Controls
//---Erzeugung der Controls:
//    Hauptmenü
//--- Kontextmenüs
//--- Einfache buttons
//--- Icon Buttons
//--- Split buttons
   if(!CreateSplitButton1("Split Button 1"))
      return(false);
   if(!CreateSplitButton2("Split Button 2"))
      return(false);
   if(!CreateSplitButton3("Split Button 3"))
      return(false);
   if(!CreateSplitButton4("Split Button 4"))
      return(false);
//--- Neuzeichnen auf dem Chart
   m_chart.Redraw();
   return(true);
  }

Wir verwenden nun einen von denen als ein Beispiel und zeigen ihn in dem nachfolgenden Code. Bitte beachten Sie, dassVor dem Festlegen der Eigenschaften des Kontextmenü, der Pointer abgefragt werden muss. Dazu verwenden Sie die CSplitButton::GetContextMenuPointer() Methode.

//+-----------------------------------------------------------------+
//| Erzeugt den Split button 1                                      |
//+-----------------------------------------------------------------+
bool CProgram::CreateSplitButton1(const string button_text)
  {
//--- Drei Punkte im Kontextmenü
#define CONTEXTMENU_ITEMS5 3
//--- Übergabe des Panel Objektes
   m_split_button1.WindowPointer(m_window);
//--- Koordinaten
   int x=m_window.X()+SPLITBUTTON1_GAP_X;
   int y=m_window.Y()+SPLITBUTTON1_GAP_Y;
//--- Array Mit den Namen der Menüpunkte
   string items_text[]=
     {
      "Item 1",
      "Item 2",
      "Item 3"
     };
//--- Array mit den Icons für den Verfügbarkeits-Modus
   string items_bmp_on[]=
     {
      "Images\\EasyAndFastGUI\\Icons\\bmp16\\coins.bmp",
      "Images\\EasyAndFastGUI\\Icons\\bmp16\\line_chart.bmp",
      "Images\\EasyAndFastGUI\\Icons\\bmp16\\bar_chart.bmp"
     };
//--- Array von Icons für den gesperrt Modus 
   string items_bmp_off[]=
     {
      "Images\\EasyAndFastGUI\\Icons\\bmp16\\coins_colorless.bmp",
      "Images\\EasyAndFastGUI\\Icons\\bmp16\\line_chart_colorless.bmp",
      "Images\\EasyAndFastGUI\\Icons\\bmp16\\bar_chart_colorless.bmp"
     };
//--- Festlegen der Eigenschaften bevor er erzeugt wird
   m_split_button1.ButtonXSize(116);
   m_split_button1.ButtonYSize(22);
   m_split_button1.DropButtonXSize(16);
   m_split_button1.LabelColor(clrBlack);
   m_split_button1.LabelColorPressed(clrBlack);
   m_split_button1.BackColor(clrGainsboro);
   m_split_button1.BackColorHover(C'193,218,255');
   m_split_button1.BackColorPressed(C'190,190,200');
   m_split_button1.BorderColor(C'150,170,180');
   m_split_button1.BorderColorOff(C'178,195,207');
   m_split_button1.BorderColorHover(C'150,170,180');
   m_split_button1.IconFileOn("Images\\EasyAndFastGUI\\Icons\\bmp16\\script.bmp");
   m_split_button1.IconFileOff("Images\\EasyAndFastGUI\\Icons\\bmp16\\script_colorless.bmp");
//--- Erhalten des Pointers zu dem Kontextmenü des Buttons
   CContextMenu *cm=m_split_button1.GetContextMenuPointer();
//--- Festlegen der Eigenschaften des Kontextmenüs
   cm.AreaBackColor(C'240,240,240');
   cm.AreaBorderColor(clrSilver);
   cm.ItemBackColor(C'240,240,240');
   cm.ItemBorderColor(C'240,240,240');
   cm.LabelColor(clrBlack);
   cm.LabelColorHover(clrWhite);
   cm.SeparateLineDarkColor(C'160,160,160');
   cm.SeparateLineLightColor(clrWhite);
//--- Hinzufügen von Menüpunkten zu dem Kontextmenü
   for(int i=0; i<CONTEXTMENU_ITEMS5; i++)
      m_split_button1.AddItem(items_text[i],items_bmp_on[i],items_bmp_off[i]);
//--- Trennlinie nach dem ersten Menüpunkt
   m_split_button1.AddSeparateLine(1);
//--- Erzeugung des Controls
   if(!m_split_button1.CreateSplitButton(m_chart_id,button_text,m_subwin,x,y))
      return(false);
//--- Hinzufügen des Pointers von dem Control zu der Basisklasse
   CWndContainer::AddToElementsArray(0,m_split_button1);
   return(true);
  }

 

Nach dem erfolgreichen Kompilieren und dem Starten des Programms auf einem Chart, sollten Sie das folgende Ergebnis sehen:

Fig. 6. Test des Split Button Controls.

Fig. 6. Test des Split Button Controls.

 

Die Entwicklung der Klasse für einen Split Button ist abgeschlossen. Sie können den hier dargestellten EA als Datei, die diesen Artikel beigefügt ist, herunterladen. In dem nächsten Artikel werden wir die Entwicklung von Klassen für die Erzeugung von Gruppen Buttons besprechen (Interconnected Buttons). 

 


Schlussfolgerung

Dieser Artikel hat sich damit beschäftigt, einen simplen und multifunktionalen Button zu erzeugen. In den nächsten Artikel werden wir unsere Bibliothek mit der Klasse für die Erzeugung von kopierten Buttons bereichern.

Die angefügten Dateiarchive beinhalten die Dateien, die dem aktuellen Entwicklungsstatus dieses Projektes entsprechen, so wie die Bilder, die in diesem Artikel verwendet worden sind. Sie können diese zum Test in dem MetaTrader Terminals herunterladen. 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 dritten Teils:

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/2296

Beigefügte Dateien |
Graphische Interfaces III: Gruppen von einfachen und multifunktionalen Buttons (Kapitel 2) Graphische Interfaces III: Gruppen von einfachen und multifunktionalen Buttons (Kapitel 2)
In dem ersten Kapitel dieser Serie ging es um einfache und multifunktionelle Buttons. Der zweite Artikel handelt über Gruppen von interagierenden Buttons, mit denen der Programmierer Elemente erzeugen kann, bei der der Anwender ein Element aus der Gruppe auswählen kann.
Erweiterung des StrategieTesters um ausschließlich Indikatoren zu optimieren am Beispiel von Seitwärts- und Trend-Märkten Erweiterung des StrategieTesters um ausschließlich Indikatoren zu optimieren am Beispiel von Seitwärts- und Trend-Märkten
Es ist für viele Strategien essentiell zu erkennen, ob ein Markt 'flach' ist oder nicht. Mithilfe des bekannten ADX zeigen wir, wie wir den Strategie-Tester nicht nur für die Optimierung dieses Indikators für unseren speziellen Zweck verwenden können, wir können auch entscheiden, ob dieser Indikator unserem Ziel gerecht wird und wir können die durchschnittliche Spanne eines Seitwärts-Marktes und eines Trend-Marktes ermitteln, welches wichtig für die Abschätzung von Stopps und Kurszielen werden könnte.
Grafische Interfaces IV: Informierende Interface-Elemente (Kapitel 1) Grafische Interfaces IV: Informierende Interface-Elemente (Kapitel 1)
Zum aktuellen Stand der Entwicklung, beinhaltet die Bibliothek für die Erzeugung von grafischen Interfaces ein Formular und verschiedene Steuerelemente (Controls), welche dem Formular hinzugefügt werden können. Wie zuvor schon erwähnt, sollte sich einer der zukünftigen Artikel mit dem Thema Multi-Window-Modus beschäftigen. Dafür liegt nun alles vor, und wir werden in dem folgenden Kapitel dieses Thema behandeln. In diesem Kapitel schreiben wir Klassen für die Erzeugung der Statusbar und des Tooltip-Elementes.
Grafische Interfaces II: Das Hauptmenü Element (Kapitel 4) Grafische Interfaces II: Das Hauptmenü Element (Kapitel 4)
Dieses ist das letzte Kapitel des zweiten Teils der Serie über die grafischen Interfaces. Hier betrachten wir die Erzeugung des Hauptmenüs. Hier demonstrieren wir die Entwicklung dieses Controls und das Einrichten der Eventhandler innerhalb der Bibliotheks-Klasse, damit später die Anwendung korrekt auf die Aktionen des Users reagiert. Wir besprechen hier zudem, wie man Kontextmenüs dem Hauptmenü hinzufügt. Zudem werden wir noch besprechen, wie man inaktive Elemente blockiert.