English Русский 中文 Español 日本語 Português
Grafisches Interface XI: Texteingabefelder und Kombinationsfelder in Tabellenzellen (build 15)

Grafisches Interface XI: Texteingabefelder und Kombinationsfelder in Tabellenzellen (build 15)

MetaTrader 5Beispiele | 2 Oktober 2017, 10:42
665 0
Anatoli Kazharski
Anatoli Kazharski

Inhalt


Einführung

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

Die nächste Aktualisierung konzentriert sich auf die Tabellensteuerelemente (die Klasse CTable). Vorher wird es möglich Ankreuzkästchen und Schaltflächen zu Tabellenzellen hinzuzufügen. Erweitern wir die Palette dieser Steuerelemente mit Text- und Kombinationsfeldern. Die neue Version kann jetzt auch während der Laufzeit der Anwendung die Fenstergröße ändern.


Ändern der Fenstergröße

Um die Verwendung von Listen, Tabellen oder mehrzeiligen Textfeldern zu erleichtern, ist es oft notwendig, das Fenster auf das gesamte Chart auszudehnen oder die Größe zu verkleinern. Es gibt mehrere Wege die Fenstergröße zu ändern.

  • Ein Modus zum schnellen Umschalten von Normal- auf Vollbild und zurück mit einem einzigen Klick auf eine spezielle Taste.
  • Ein Doppelklick auf den Fenstertitel vergrößert das Fenster ebenfalls auf Vollbild. Ein erneuter Doppelklick bringt das Fenster wieder in den vorherigen Zustand.
  • Eine Größenänderung durch das Ziehen des Fensterrahmens mit der linken Maustaste.

Schauen wir uns an, wie das in der Bibliothek realisiert wurde. 

Für die Taste für den Vollbildmodus wurde eine eigene Instanz der Klasse CButton deklariert. Die 'public' Methode CButton::GetFullscreenButtonPointer() soll den Pointer auf die Taste übernehmen. Standardmäßig ist diese Taste deaktiviert. Sie wird mit der Methode CButton::FullscreenButtonIsUsed() aktiviert. 

//+------------------------------------------------------------------+
//| Anzeigenklasse für Steuerelemente                                |
//+------------------------------------------------------------------+
class CWindow : public CElement
  {
private:
   //--- Objekte dieser Anzeige 
   CButton           m_button_fullscreen;
   //--- Vorhandensein der Taste zur Fenstermaximierung auf den Vollbildmodus
   bool              m_fullscreen_button;
   //---
public:
   //--- Rückgabe des Pointers auf die Taste
   CButton          *GetFullscreenButtonPointer(void)                { return(::GetPointer(m_button_fullscreen)); }
   //--- Verwendung der Taste für den Vollbildmodus
   void              FullscreenButtonIsUsed(const bool state)        { m_fullscreen_button=state;                 }
   bool              FullscreenButtonIsUsed(void)              const { return(m_fullscreen_button);               }
  };

Die Taste für den Vollbildmodus wird in der allgemeinen Methode CWindow::CreateButtons() erzeugt (siehe den Code unten), wo alle aktivierten Tasten der Klasse erzeugt werden. Ähnlich wie bei den Tasten zum Anzeigen von Tooltips und des Minimieren der Anzeige kann die Taste für den Vollbildmodus nur im Hauptfenster verwendet werden.  

//+------------------------------------------------------------------+
//| Erstellen von Tasten für die Anzeigen                            |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Controls\\full_screen.bmp"
#resource "\\Images\\EasyAndFastGUI\\Controls\\minimize_to_window.bmp"
//---
bool CWindow::CreateButtons(void)
  {
//--- Verlassen, wenn das Programm ein Skript ist
   if(CElementBase::ProgramType()==PROGRAM_SCRIPT)
      return(true);
//--- Zähler, die Größe, Anzahl
   int i=0,x_size=20;
   int buttons_total=4;
//--- Der Pfad zu der Datei
   string icon_file="";
//--- Ausnahme im Erfassungsbereich
   m_right_limit=0;
//---
   CButton *button_obj=NULL;
//---
   for(int b=0; b<buttons_total; b++)
     {
      ...
      else if(b==1)
        {
         m_button_fullscreen.MainPointer(this);
         //--- Verlassen, wenn 1. Taste oder 2. das Dialogfeld deaktiviert sind
         if(!m_fullscreen_button || m_window_type==W_DIALOG)
            continue;
         //---
         button_obj=::GetPointer(m_button_fullscreen);
         icon_file="Images\\EasyAndFastGUI\\Controls\\full_screen.bmp";
        }
      ...
     }
//---
   return(true);
  }

Die Mindestgröße des Fensters wird automatisch auf die beim Erstellen des Steuerelements angegebene Größe gesetzt. Aber diese Werte können überschrieben werden. Aktuelle ist es aber nicht möglich des Fenster auf eine Größe kleiner als 200x200 Pixel zu setzen. Dies wird in der Methode zur Initialisierung der Eigenschaften des Steuerelements gesteuert — CWindow::InitializeProperties(). 

class CWindow : public CElement
  {
private:
   //--- Mindestgröße des Fensters
   int               m_minimum_x_size;
   int               m_minimum_y_size;
   //---
public:
   //--- Bestimmen der Mindestgröße des Fensters
   void              MinimumXSize(const int x_size)                  { m_minimum_x_size=x_size;                   }
   void              MinimumYSize(const int y_size)                  { m_minimum_y_size=y_size;                   }
  };
//+------------------------------------------------------------------+
//| Konstruktor                                                      |
//+------------------------------------------------------------------+
CWindow::CWindow(void) : m_minimum_x_size(0),
                         m_minimum_y_size(0)
  {
...
  }
//+------------------------------------------------------------------+
//| Initialisierung der Eigenschaften                                |
//+------------------------------------------------------------------+
void CWindow::InitializeProperties(const long chart_id,const int subwin,const string caption_text,const int x_gap,const int y_gap)
  {
...
   m_x_size         =(m_x_size<1)? 200 : m_x_size;
   m_y_size         =(m_y_size<1)? 200 : m_y_size;
...
   m_minimum_x_size =(m_minimum_x_size<200)? m_x_size : m_minimum_x_size;
   m_minimum_y_size =(m_minimum_y_size<200)? m_y_size : m_minimum_y_size;
...
  }

Vor dem Vergrößern des Fenster auf das Vollbild, ist es notwendig, die aktuellen Maße, Koordinaten und den Modus der automatischen Größenänderung zu speichern, falls diese festgelegt wurden. Diese Werte werden in besonderen privaten Feldern der Klasse gespeichert:

class CWindow : public CElement
  {
private:
   //--- Die letzten Koordinaten und Maße vor dem Umschalten in das Vollbild
   int               m_last_x;
   int               m_last_y;
   int               m_last_x_size;
   int               m_last_y_size;
   bool              m_last_auto_xresize;
   bool              m_last_auto_yresize;
  };

Die Methode CWindow::OnClickFullScreenButton() reagiert auf den Klick der Taste für das Vollbild. Sie prüft zuerst Identifikator und Index des Steuerelements, dann wird der Code in zwei Blöcke aufgeteilt:

  • Wenn das Fenster im Augenblick nicht maximiert ist, schalten Sie es in den Vollbildmodus um. Als nächstes werden die aktuellen Maße ermittelt, und die aktuellen Größen, Koordinaten des Fensters und die automatischen Größenänderungen in den speziellen Feldern der Klasse gespeichert. Da im Vollbildmodus die Größe der Anzeige automatisch angepasst werden muss, wenn sich die Größe des Hauptcharts ändert, muss die automatische Größenanpassung aktiviert werden. Danach werden die Fenstergrößen eingestellt. Gleichzeitig wird die Position des Fensters in die linke obere Ecke gesetzt, so dass das gesamte Chart ausfüllt wird. Das Symbol in der Taste wird durch ein anderes ersetzt.
  • Ist das Fenster gerade maximiert, schaltet es auf die vorherige Fenstergröße zurück. Jetzt wechselt auch die automatische Größenanpassung in den vorherigen Modus zurück. Je nach dem welcher Modus deaktiviert ist, wird die vorherigen Fenstergröße eingestellt. Auch die vorige Position, und das entsprechende Symbol für die Taste werden festgelegt.

Am Ende der Methode CWindow::OnClickFullScreenButton() wird die Anzeige neu gezeichnet:

class CWindow : public CElement
  {
private:
   //--- Status des Fensters im Vollbild
   bool              m_is_fullscreen;
   //---
public:
   //--- Wechseln zum Vollbild oder zur vorherigen Fenstergröße
   bool              OnClickFullScreenButton(const int id=WRONG_VALUE,const int index=WRONG_VALUE);
  };
//+------------------------------------------------------------------+
//| Wechseln zum Vollbild oder zur vorherigen Fenstergröße           |
//+------------------------------------------------------------------+
bool CWindow::OnClickFullScreenButton(const int id=WRONG_VALUE,const int index=WRONG_VALUE)
  {
//--- Prüfen der Identifikatoren und Indices des Steuerelements bei einem externen Aufruf
   int check_id    =(id!=WRONG_VALUE)? id : CElementBase::Id();
   int check_index =(index!=WRONG_VALUE)? index : CElementBase::Index();
//--- Verlassen, wenn die Indices nicht übereinstimmen
   if(check_id!=m_button_fullscreen.Id() || check_index!=m_button_fullscreen.Index())
      return(false);
//--- Falls das Fenster nicht im Vollbild ist
   if(!m_is_fullscreen)
     {
      //--- Wechseln zum Vollbild
      m_is_fullscreen=true;
      //--- Abfrage der aktuellen Maße des Chartfensters
      SetWindowProperties();
      //--- Sichern der aktuellen Koordinaten und Maße der Anzeige
      m_last_x            =m_x;
      m_last_y            =m_y;
      m_last_x_size       =m_x_size;
      m_last_y_size       =m_full_height;
      m_last_auto_xresize =m_auto_xresize_mode;
      m_last_auto_yresize =m_auto_yresize_mode;
      //--- Aktivieren der autom. Größenanpassung der Anzeige
      m_auto_xresize_mode=true;
      m_auto_yresize_mode=true;
      //--- Vergrößern der Anzeige auf den ganze Chartfenster
      ChangeWindowWidth(m_chart.WidthInPixels()-2);
      ChangeWindowHeight(m_chart.HeightInPixels(m_subwin)-3);
      //--- Aktualisieren der Lage
      m_x=m_y=1;
      Moving(m_x,m_y);
      //--- Ersetzen des Symbols der Taste
      m_button_fullscreen.IconFile("Images\\EasyAndFastGUI\\Controls\\minimize_to_window.bmp");
      m_button_fullscreen.IconFileLocked("Images\\EasyAndFastGUI\\Controls\\minimize_to_window.bmp");
     }
//--- Falls das Fenster im Vollbild ist
   else
     {
      //--- Wechseln zur vorherigen Fenstergröße
      m_is_fullscreen=false;
      //--- Deaktivieren der autom. Größenanpassung
      m_auto_xresize_mode=m_last_auto_xresize;
      m_auto_yresize_mode=m_last_auto_yresize;
      //--- Wenn der Modus deaktiviert ist, einstellen der vorherigen Größe
      if(!m_auto_xresize_mode)
         ChangeWindowWidth(m_last_x_size);
      if(!m_auto_yresize_mode)
         ChangeWindowHeight(m_last_y_size);
      //--- Aktualisieren der Lage
      m_x=m_last_x;
      m_y=m_last_y;
      Moving(m_x,m_y);
      //--- Ersetzen des Symbols der Taste
      m_button_fullscreen.IconFile("Images\\EasyAndFastGUI\\Controls\\full_screen.bmp");
      m_button_fullscreen.IconFileLocked("Images\\EasyAndFastGUI\\Controls\\full_screen.bmp");
     }
//--- Entfernen des Fokus von der Taste
   m_button_fullscreen.MouseFocus(false);
   m_button_fullscreen.Update(true);
   return(true);
  }

Die Methode CWindow::OnClickFullScreenButton() wird in der Ereignisbehandlung des Steuerelements aufgerufen, wenn das Ereignis ON_CLICK_BUTTON auftritt. 

//+------------------------------------------------------------------+
//| Ereignisbehandlung des Charts                                    |
//+------------------------------------------------------------------+
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Reagieren auf den Klick auf die Taste
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      ...
      //--- Prüfen des Vollbildmodus
      if(OnClickFullScreenButton((uint)lparam,(uint)dparam))
         return;
      ...
      //---
      return;
     }
  }

Das Ergebnis sieh dann so aus:

 Fig. 1. Demonstration des Umschaltens auf Vollbild und zurück.


Fig. 1. Demonstration des Umschaltens auf Vollbild und zurück.


Um durch einen Doppelklick auf den Fenstertitel in den Vollbildmodus und zurück zu wechseln, genügt es nun, dieses Doppelklickereignis (ON_DOUBLE_CLICK) auf den Fenstertitel in der Ereignisbehandlung des Steuerelements zu verarbeiten.

void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Ereignisbehandlung eines Doppelklicks auf ein Objekt
   if(id==CHARTEVENT_CUSTOM+ON_DOUBLE_CLICK)
     {
      //--- Wenn das Ereignis in der Leiste des Fenstertitels geschah
      if(CursorInsideCaption(m_mouse.X(),m_mouse.Y()))
         OnClickFullScreenButton(m_button_fullscreen.Id(),m_button_fullscreen.Index());
      //---
      return;
     }
  }

Und so schaut alles aus:

 Fig. 2. Demonstration des Umschaltens auf Vollbild durch einen Doppelklick auf die Titelleiste.


Fig. 2. Demonstration des Umschaltens auf Vollbild durch einen Doppelklick auf die Titelleiste.


Kommen wir nun zur Größenänderung durch das Ziehen des Fensterrahmens. Die Methode CWindow::ResizeMode() ermöglicht das.

class CWindow : public CElement
  {
private:
   //--- Modus der Größenänderung des Fensters
   bool              m_xy_resize_mode;
   //---
public:
   //--- Möglichkeit zur Größenänderung des Fensters
   bool              ResizeMode(void)                          const { return(m_xy_resize_mode);                  }
   void              ResizeMode(const bool state)                    { m_xy_resize_mode=state;                    }
  };
//+------------------------------------------------------------------+
//| Konstruktor                                                      |
//+------------------------------------------------------------------+
CWindow::CWindow(void) : m_xy_resize_mode(false)
  {
...
  }

Um den Klick mit der linken Maustaste auf die Fensterränder zu erfassen, ist ein weiterer Identifikator (PRESSED_INSIDE_BORDER) in der ENUM_MOUSE_STATE Enumeration erforderlich, die sich in der Datei Enums.mqh befindet.

//+------------------------------------------------------------------+
//|                                                        Enums.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Enumeration der Bereiche für die linke Maustaste                 |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_STATE
  {
   NOT_PRESSED           =0,
   PRESSED_INSIDE        =1,
   PRESSED_OUTSIDE       =2,
   PRESSED_INSIDE_HEADER =3,
   PRESSED_INSIDE_BORDER =4
  };

 Ist der Modus zur Größenänderung aktiviert, wird für den Mauszeiger ein grafisches Objekt mit dem neuen Identifikator MP_WINDOW_RESIZE aus der Enumeration ENUM_MOUSE_POINTER erzeugt.

//+------------------------------------------------------------------+
//| Enumeration der Pointertypen                                     |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_POINTER
  {
   MP_CUSTOM            =0,
   MP_X_RESIZE          =1,
   MP_Y_RESIZE          =2,
   MP_XY1_RESIZE        =3,
   MP_XY2_RESIZE        =4,
   MP_WINDOW_RESIZE     =5,
   MP_X_RESIZE_RELATIVE =6,
   MP_Y_RESIZE_RELATIVE =7,
   MP_X_SCROLL          =8,
   MP_Y_SCROLL          =9,
   MP_TEXT_SELECT       =10
  };

Die Kasse CWindow wurde um die Methode CreateResizePointer() erweitert, um das grafische Objekt für den Mauszeiger zu erzeugen:

class CWindow : public CElement
  {
private:
   bool              CreateResizePointer(void);
  };
//+------------------------------------------------------------------+
//| Erstellen des Mauskursors für die Größenänderung                 |
//+------------------------------------------------------------------+
bool CWindow::CreateResizePointer(void)
  {
//--- Verlassen, wenn der Modus zur Größenänderung deaktiviert ist
   if(!m_xy_resize_mode)
      return(true);
//--- Eigenschaften
   m_xy_resize.XGap(13);
   m_xy_resize.YGap(11);
   m_xy_resize.XSize(23);
   m_xy_resize.YSize(23);
   m_xy_resize.Id(CElementBase::Id());
   m_xy_resize.Type(MP_WINDOW_RESIZE);
//--- Erstellen der Textfelder
   if(!m_xy_resize.CreatePointer(m_chart_id,m_subwin))
      return(false);
//---
   return(true);
  }

Es war notwendig, mehrere Methoden zur Größenänderung des Fensters zu implementieren. Betrachten wir sie der Reihe nach.

Die Position des Mauszeigers muss verfolgt werden, wenn er im Fensterbereich erscheint. In dieser Version kann die Größe des Fensters durch Ziehen des linken, rechten oder unteren Rahmens verändert werden. Die Methode CWindow:: ResizeModeIndex() verfolgt den Fokus über einen der aufgelisteten Rahmen und speichert dessen Index zur späteren Behandlung in anderen Methoden. Die Koordinaten des Mauskursors relativ zum Fenster werden dieser Methode für weitere Berechnungen übergeben.

class CWindow : public CElement
  {
private:
   //--- Der Index des Rahmens für die Größenänderung des Fensters
   int               m_resize_mode_index;
   //---
private:
   //--- Rückgabe des Index des Modus für die Größenänderung des Fensters
   int               ResizeModeIndex(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Rückgabe des Index des Modus für die Größenänderung des Fensters |
//+------------------------------------------------------------------+
int CWindow::ResizeModeIndex(const int x,const int y)
  {
//--- Rückgabe des Index des Modus, wenn bereits gezogen wird
   if(m_resize_mode_index!=WRONG_VALUE && m_mouse.LeftButtonState())
      return(m_resize_mode_index);
//--- Breite, Abstand und Index des Rahmens
   int width  =5;
   int offset =15;
   int index  =WRONG_VALUE;
//--- Prüfen des Fokus auf den linken Rahmen
   if(x>0 && x<width && y>m_caption_height+offset && y<m_y_size-offset)
      index=0;
//--- Prüfen des Fokus auf den rechten Rahmen
   else if(x>m_x_size-width && x<m_x_size && y>m_caption_height+offset && y<m_y_size-offset)
      index=1;
//--- Prüfen des Fokus auf den unteren Rahmen
   else if(y>m_y_size-width && y<m_y_size && x>offset && x<m_x_size-offset)
      index=2;
//--- Wenn der erhaltene Index den geklickten Bereich betrifft
   if(index!=WRONG_VALUE)
      m_clamping_area_mouse=PRESSED_INSIDE_BORDER;
//--- Rückgabe des Index des Bereiches
   return(index);
  }

Es werden weitere Variablen in der Klasse benötigt: für die Bestimmung der Erfassungspunkte, für die Speicherung der Anfangsmaße und für spätere Berechnungen. Sobald die Größenänderung beginnt, muss eine Meldung zur Bildung der Liste der verfügbaren Steuerelemente erzeugt werden. Daher wird auch eine Methode benötigt, die die Nachricht erstellt, das Steuerelement wiederherstellt und die Variablen zurücksetzt: CWindow::ZeroResizeVariables().

class CWindow : public CElement
  {
private:
   //--- Variablen der Größenänderung des Fensters
   int               m_x_fixed;
   int               m_size_fixed;
   int               m_point_fixed;
   //---
private:
   //--- Nullstellen der Variablen
   void              ZeroResizeVariables(void);
  };
//+------------------------------------------------------------------+
//| Rücksetzten der Variablen der Größenänderung des Fensters        |
//+------------------------------------------------------------------+
void CWindow::ZeroResizeVariables(void)
  {
//--- Verlassen, wenn bereits auf Null gestellt
   if(m_point_fixed<1)
      return;
//--- Null
   m_x_fixed     =0;
   m_size_fixed  =0;
   m_point_fixed =0;
//--- Senden einer Nachricht, um die verfügbaren Steuerelemente wiederherzustellen
   ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),1,"");
//--- Senden einer Nachricht über die Veränderung des grafischen Interfaces
   ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,"");
  }

Die Methode CWindow::CheckResizePointer() wurde implementiert, um festzustellen, ob der Mauszeiger zum Anzeigen und Ausblenden des Fensters bereit ist. Hier wird die Methode CWindow::ResizeModeIndex() verwendet, um den Index des Rahmens zu bestimmen. 

Wenn der Mauszeiger noch nicht angezeigt wird, muss man bei einem bestimmten Rahmenindex das entsprechende Symbol setzen, die Position anpassen und den Pointer ausgeben.

Falls der ermittelte Rahmenindex ergibt, dass der Mauszeiger bereits angezeigt wird, wird er zum Mauszeiger verschoben, wenn der Fokus auf einem der Rahmen liegt. Wenn kein Fokus vorhanden ist und die linke Maustaste losgelassen wird, wird der Cursor ausgeblendet und die Variablen auf Null gesetzt.

Die Methode CWindow::CheckResizePointer() gibt true zurück, wenn der Fensterrahmen für eine Größenänderung definiert ist, ansonsten false.

class CWindow : public CElement
  {
private:
   //--- Prüfen der Bereitschaft für die Größenänderung des Fensters
   bool              CheckResizePointer(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Prüfen der Bereitschaft für die Größenänderung des Fensters      |
//+------------------------------------------------------------------+
bool CWindow::CheckResizePointer(const int x,const int y)
  {
//--- Bestimmen des aktuellen Rahmenindex
   m_resize_mode_index=ResizeModeIndex(x,y);
//--- Falls der Kursor ausgeblendet ist
   if(!m_xy_resize.IsVisible())
     {
      //--- Falls der Rahmen definiert istr
      if(m_resize_mode_index!=WRONG_VALUE)
        {
         //--- Zur Bestimmung des Index des angezeigten Symbols des Mauskursors
         int index=WRONG_VALUE;
         //--- Falls auf einem senkrechten Rahmen
         if(m_resize_mode_index==0 || m_resize_mode_index==1)
            index=0;
         //--- Falls auf einem waagerechten Rahmen
         else if(m_resize_mode_index==2)
            index=1;
         //--- Wechseln des Symbols
         m_xy_resize.ChangeImage(0,index);
         //--- Verschieben, Neuzeichnen und Anzeigen
         m_xy_resize.Moving(m_mouse.X(),m_mouse.Y());
         m_xy_resize.Update(true);
         m_xy_resize.Reset();
         return(true);
        }
     }
   else
     {
      //--- Verschieben des Kursors
      if(m_resize_mode_index!=WRONG_VALUE)
         m_xy_resize.Moving(m_mouse.X(),m_mouse.Y());
      //--- Ausblenden des Kursors
      else if(!m_mouse.LeftButtonState())
        {
         //--- Ausblenden des Kursors und Rücksetzen der Variablen
         m_xy_resize.Hide();
         ZeroResizeVariables();
        }
      //--- Neuzeichnen des Charts
      m_chart.Redraw();
      return(true);
     }
//---
   return(false);
  }

Die Methode CWindow::CheckDragWindowBorder() wird verwendet, um den Beginn des Ziehens eines Fensterrahmens zu überprüfen. Sobald der Rahmen gezogen wird, müssen die aktuellen Maße und die Koordinate des ersten Zielpunkts in den Variablen der Klasse gespeichert werden. Gleichzeitig wird eine Meldung zur Ermittlung der verfügbaren Steuerelemente gesendet

Wenn die nachfolgenden Aufrufe dieser Methode zeigen, dass der Rahmen bereits gezogen wird, dann muss die in diesem Modus zurückgelegte Strecke berechnet und der resultierenden Wert zurückzugeben werden.

class CWindow : public CElement
  {
private:
   //--- Prüfen des Ziehens des Fensterrahmens
   int               CheckDragWindowBorder(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Prüfen des Ziehens des Fensterrahmens                            |
//+------------------------------------------------------------------+
int CWindow::CheckDragWindowBorder(const int x,const int y)
  {
//--- Bestimmen der zurückgelegten Strecke
   int distance=0;
//--- Falls das Ziehen des Rahmen noch nicht bekannt ist
   if(m_point_fixed<1)
     {
      //--- Falls entlang der X-Achse die Größe geändert wird
      if(m_resize_mode_index==0 || m_resize_mode_index==1)
        {
         m_x_fixed     =m_x;
         m_size_fixed  =m_x_size;
         m_point_fixed =x;
        }
      //--- Falls entlang der Y-Achse die Größe geändert wird
      else if(m_resize_mode_index==2)
        {
         m_size_fixed  =m_y_size;
         m_point_fixed =y;
        }
      //--- Senden eine Nachricht der verfügbaren Steuerelemente
      ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),0,"");
      //--- Senden einer Nachricht über die Veränderung des grafischen Interfaces
      ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,"");
      return(0);
     }
//--- Falls es der linke Rahmen ist
   if(m_resize_mode_index==0)
      distance=m_mouse.X()-m_x_fixed;
//--- Falls es der rechte Rahmen ist
   else if(m_resize_mode_index==1)
      distance=x-m_point_fixed;
//--- Falls es der untere Rahmen ist
   else if(m_resize_mode_index==2)
      distance=y-m_point_fixed;
//--- Rückgabe der zurückgelegten Strecke
   return(distance);
  }

Das von der Methode CWindow::CheckDragWindowBorder() zurückgegebene Ergebnis wird an die Methode CWindow::CalculateAndResizeWindow() übergeben, die die Fensterkoordinaten und -dimensionen relativ zu ihrem Rahmen berechnet. 

class CWindow : public CElement
  {
private:
   //--- Berechnen und Ändern der Fenstergröße
   void              CalculateAndResizeWindow(const int distance);
  };
//+------------------------------------------------------------------+
//| Berechnen und Ändern der Fenstergröße                            |
//+------------------------------------------------------------------+
void CWindow::CalculateAndResizeWindow(const int distance)
  {
//--- Linker Rahmen
   if(m_resize_mode_index==0)
     {
      int new_x      =m_x_fixed+distance-m_point_fixed;
      int new_x_size =m_size_fixed-distance+m_point_fixed;
      //--- Verlassen, wenn die Grenzen überschritten wurden
      if(new_x<1 || new_x_size<=m_minimum_x_size)
         return;
      //--- Koordinaten
      CElementBase::X(new_x);
      m_canvas.X_Distance(new_x);
      //--- Setzen und sichern der Größe
      CElementBase::XSize(new_x_size);
      m_canvas.XSize(new_x_size);
      m_canvas.Resize(new_x_size,m_canvas.YSize());
     }
//--- Rechter Rahmen
   else if(m_resize_mode_index==1)
     {
      int gap_x2     =m_chart_width-m_mouse.X()-(m_size_fixed-m_point_fixed);
      int new_x_size =m_size_fixed+distance;
      //--- Verlassen, wenn die Grenzen überschritten wurden
      if(gap_x2<1 || new_x_size<=m_minimum_x_size)
         return;
      //--- Setzen und sichern der Größe
      CElementBase::XSize(new_x_size);
      m_canvas.XSize(new_x_size);
      m_canvas.Resize(new_x_size,m_canvas.YSize());
     }
//--- Unterer Rahmen
   else if(m_resize_mode_index==2)
     {
      int gap_y2=m_chart_height-m_mouse.Y()-(m_size_fixed-m_point_fixed);
      int new_y_size=m_size_fixed+distance;
      //--- Verlassen, wenn die Grenzen überschritten wurden
      if(gap_y2<2 || new_y_size<=m_minimum_y_size)
         return;
      //--- Setzen und sichern der Größe
      m_full_height=new_y_size;
      CElementBase::YSize(new_y_size);
      m_canvas.YSize(new_y_size);
      m_canvas.Resize(m_canvas.XSize(),new_y_size);
     }
  }


Die Methoden CWindow::CheckDragWindowBorder() und CWindow::CheckDragWindowBorder() werden innerhalb der Methode CWindow::UpdateSize() aufgerufen. Hier wird am Anfang der Methode geprüft, ob die linke Maustaste gedrückt ist. Wenn die Taste losgelassen wird, werden alle Werte der Variablen, die sich auf die Änderung der Fenstergröße beziehen, zurückgesetzt und das Programm verlässt die Methode.

Wird die linke Maustaste gedrückt, dann muss 1. der Abstand bestimmt werden, den der gezogene Rahmen zurückgelegt hat, 2. das Fenster berechnet und verändert werden, 3. das Fenster neu gezeichnet und 4. die Position seiner Elemente angepasst werden.

Am Ende der Methode wird je nach der Achse, auf der das Fenster verändert wurde, ein Ereignis erzeugt, das später für die Größenänderung aller Steuerelemente verwendet wird, die dem Fenster zugeordnet sind und deren entsprechender Modus aktiviert wurde.

class CWindow : public CElement
  {
private:
   //--- Aktualisierung der Fenstergrößen
   void              UpdateSize(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Aktualisierung der Fenstergrößen                                 |
//+------------------------------------------------------------------+
void CWindow::UpdateSize(const int x,const int y)
  {
//--- Falls beendet und die linke Maustaste losgelassen, Rücksetzen der Werte
   if(!m_mouse.LeftButtonState())
     {
      ZeroResizeVariables();
      return;
     }
//--- Verlassen, wenn Erfassen und Verschieben des Rahmens noch nicht begonnen hat
   int distance=0;
   if((distance=CheckDragWindowBorder(x,y))==0)
      return;
//--- Berechnen und Ändern der Fenstergröße
   CalculateAndResizeWindow(distance);
//--- Neuzeichnen des Fensters
   Update(true);
//--- Aktualisieren der Position der Objekte
   Moving(m_x,m_y);
//--- Erstellen einer Nachricht, dass die Fenstergröße verändert wurde
   if(m_resize_mode_index==2)
      ::EventChartCustom(m_chart_id,ON_WINDOW_CHANGE_YSIZE,(long)CElementBase::Id(),0,"");
   else
      ::EventChartCustom(m_chart_id,ON_WINDOW_CHANGE_XSIZE,(long)CElementBase::Id(),0,"");
  }

Alle aufgeführten Methoden zur Messung der Fenstermaße werden in der Hauptmethode CWindow::ResizeWindow() aufgerufen. Sie prüft zunächst, ob das Fenster verfügbar ist. Wenn die linke Maustaste dann nicht über einen der Fensterrahmen gedrückt wurde, verlässt das Programm die Methode. Dann folgen drei weitere Überprüfungen: 1. wurde der Modus zur Größenänderung aktiviert, 2. ist das Fenster im Vollbild und 3. ist es nicht minimiert.

Wenn alle Prüfungen bestanden worden sind, dann werden die relativen Koordinaten des Mauskursors ermittelt, es wird der Fensterrahmen erfasst, und es wird das Steuerelement verkleinert.

class CWindow : public CElement
  {
private:
   //--- Steuert die Fenstergröße
   void              ResizeWindow(void);
  };
//+------------------------------------------------------------------+
//| Steuert die Fenstergröße                                         |
//+------------------------------------------------------------------+
void CWindow::ResizeWindow(void)
  {
//--- Verlassen, wenn das Fenster nicht verfügbar ist
   if(!IsAvailable())
      return;
//--- Verlassen, wenn die Maustaste nicht über der Anzeige gedrückt wurde
   if(m_clamping_area_mouse!=PRESSED_INSIDE_BORDER && m_clamping_area_mouse!=NOT_PRESSED)
      return;
//--- Verlassen, wenn 1. der Modus zur Änderung der Fenstergröße deaktiviert oder 
//    2. das Fenster im Vollbildmodus oder 3. das Fenster minimiert ist.
   if(!m_xy_resize_mode || m_is_fullscreen || m_is_minimized)
      return;
//--- Koordinaten
   int x =m_mouse.RelativeX(m_canvas);
   int y =m_mouse.RelativeY(m_canvas);
//--- Prüfen der Bereitschaft zur Veränderungen der Breite von Listen
   if(!CheckResizePointer(x,y))
      return;
//--- Aktualisierung der Fenstergrößen
   UpdateSize(x,y);
  }

Die Methode CWindow::ResizeWindow() wird in der Ereignisbehandlung aufgerufen, wenn das Ereignis einer Bewegung des Mauskursors auftritt (CHARTEVENT_MOUSE_MOVE). 

Und so schaut alles aus:

 Fig. 3. Demonstration der Größenänderung des Fensters durch das Ziehen des Rahmens.


Fig. 3. Demonstration der Größenänderung des Fensters durch das Ziehen des Rahmens.



Text- und Kombinationsfelder in Tabellenzellen

Wenn Tabellenzellen unterschiedliche Steuerelemente haben, wird die Tabelle zu einem sehr flexiblen Werkzeug zur Verwaltung der darin enthaltenen Daten. Die nächstliegendsten Beispiele sind direkt im Handelsterminal des MetaTraders zu sehen. Es sind dies die Fenster für die Einstellungen der Eingabeparameter eines Programms für den Chart und für den Strategietester des Terminals. Grafische Interfaces mit solchen Fähigkeiten bringen MQL-Anwendungen auf ein neues Niveau.

 Fig. 4. Fenster der Einstellungen eines MQL-Programms.


Fig. 4. Fenster der Einstellungen eines MQL-Programms.


 Fig. 5. Fenster der Einstellungen eines MQL-Programms für den Strategietester.


Fig. 5. Fenster der Einstellungen eines MQL-Programms für den Strategietester.


Einer der vorherigen Artikel ermöglicht Ankreuzkästchen und Schaltflächen in Tabellenzellen. Betrachten wir nun die Implementierung von Texteditoren und Kombinationsfeldern. 

Zuerst wurden in der Datei Enum.mqh der Enumeration ENUM_TYPE_CELL zwei neue Identifikatoren hinzugefügt, um die Typen der Tabellenzellen zu kennzeichnen:

  • CELL_COMBOBOX – Eine Zelle des Typs Kombinationsfeld.
  • CELL_EDIT – Eine Zelle des Typs Texteingabefeld.
//+------------------------------------------------------------------+
//| Enumeration der Typen der Tabellenzellen                         |
//+------------------------------------------------------------------+
enum ENUM_TYPE_CELL
  {
   CELL_SIMPLE   =0,
   CELL_BUTTON   =1,
   CELL_CHECKBOX =2,
   CELL_COMBOBOX =3,
   CELL_EDIT     =4
  };

Zur Umsetzung des Geplanten genügt es, in der Tabelle nur ein Texteingabefeld (CTextEdit) und/oder ein Kombinationsfeld (CComboBox) als Bestandteil des Steuerelements CTable zu erzeugen. Sie erscheinen beim Doppelklick auf eine Zelle, wenn deren Wert geändert werden soll. 

Bei der Einstellung des Zelltyps mit der Methode CTable::CellType() muss einmal der Merker in den Extravariablen der Klasse gesetzt werden, wenn der Typ CELL_EDIT oder CELL_COMBOBOX angegeben ist.

//+------------------------------------------------------------------+
//| Kasse des Erzeugens eine Tabellendarstellung                     |
//+------------------------------------------------------------------+
class CTable : public CElement
  {
private:
   //--- Vorhandensein von Tabellenzellen mit Texteditoren und Kombinationsfeldern
   bool              m_edit_state;
   bool              m_combobox_state;
   //---
public:
   //--- Bestimmen/Abfragen des Zelltyps
   void              CellType(const uint column_index,const uint row_index,const ENUM_TYPE_CELL type);
  };
//+------------------------------------------------------------------+
//| Bestimmen des Zelltyps                                           |
//+------------------------------------------------------------------+
void CTable::CellType(const uint column_index,const uint row_index,const ENUM_TYPE_CELL type)
  {
//--- Prüfen der Arraygrenze
   if(!CheckOutOfRange(column_index,row_index))
      return;
//--- Bestimmen des Zelltyps
   m_columns[column_index].m_rows[row_index].m_type=type;
//--- Vorzeichen eines Texteingabefeldes
   if(type==CELL_EDIT && !m_edit_state)
      m_edit_state=true;
//--- Vorzeichen eines Kombinationsfeldes
   else if(type==CELL_COMBOBOX && !m_combobox_state)
      m_combobox_state=true;
  }

Wenn sich beim Anlegen einer Tabelle herausstellt, dass bei keiner Zelle CELL_EDIT oder CELL_COMBOBOX gesetzt wurden, werden die Steuerelemente der entsprechenden Typen nicht erzeugt. Bei Bedarf können die Pointer auf diese Steuerelemente abgefragt werden.

class CTable : public CElement
  {
private:
   //--- Objekte für das Erstellen einer Tabelle
   CTextEdit         m_edit;
   CComboBox         m_combobox;
   //---
private:
   bool              CreateEdit(void);
   bool              CreateCombobox(void);
   //---
public:
   //--- Rückgabe des Pointers auf das Steuerelement
   CTextEdit        *GetTextEditPointer(void)                { return(::GetPointer(m_edit));     }
   CComboBox        *GetComboboxPointer(void)                { return(::GetPointer(m_combobox)); }
  };

Bei einem Doppelklick auf die Tabelle wird die Methode CTable::CheckCellElement() aufgerufen. Sie enthält die entsprechenden Ergänzungen für die Zellen der Typen CELL_EDIT und CELL_COMBOBOX. Der Code unten zeigt verkürzt diese Methode. Die Methoden zur Handhabung verschiedener Zelltypen werden im Folgenden detailliert beschrieben.

//+------------------------------------------------------------------+
//| Prüfen, ob das Steuerelement beim Klick aktiv ist                |
//+------------------------------------------------------------------+
bool CTable::CheckCellElement(const int column_index,const int row_index,const bool double_click=false)
  {
//--- Verlassen, wenn die Zelle gar kein Steuerelement hat
   if(m_columns[column_index].m_rows[row_index].m_type==CELL_SIMPLE)
      return(false);
//---
   switch(m_columns[column_index].m_rows[row_index].m_type)
     {
      ...
      //--- Falls dies eine Zelle mit einem Texteingabefeld ist
      case CELL_EDIT :
        {
         if(!CheckPressedEdit(column_index,row_index,double_click))
            return(false);
         //---
         break;
        }
      //--- Falls dies eine Zelle mit einem Kombinationsfeld ist
      case CELL_COMBOBOX :
        {
         if(!CheckPressedCombobox(column_index,row_index,double_click))
            return(false);
         //---
         break;
        }
     }
//---
   return(true);
  }

Bevor wir nun mit der Betrachtung der Methoden zur Handhabung von Klicks auf Tabellenzellen mit Steuerelementen fortfahren, soll noch auf die Ergänzungen der Steuerelemente vom Typ CTextBox eingegangen werden. Manchmal ist es notwendig, dass der gesamte Text in einem Textfeld automatisch ausgewählt wird, wenn das Textfeld aktiviert wird, und der Textkursor an das Zeilenende bewegt wird. Dies ist praktisch, um ganze Textteile schnell einzugeben und ersetzen zu können. 

Die automatische Textauswahl in der aktuellen Version funktioniert nur für ein einzeiliges Textfeld. Dieser Modus kann mit der Methode CTextBox::AutoSelectionMode() aktiviert werden. 

//+------------------------------------------------------------------+
//| Klasse zur Erstellung eines mehrzeiligen Textfeldes              |
//+------------------------------------------------------------------+
class CTextBox : public CElement
  {
private:
   //--- Modus zur automatischen Textauswahl
   bool              m_auto_selection_mode;
   //---
public:
   //--- Modus zur automatischen Textauswahl
   void              AutoSelectionMode(const bool state)       { m_auto_selection_mode=state;     }
  };

Die 'private' Methode CTextBox::SelectAllText() wurde implementiert, um den ganzen Text eines Textfeldes auszuwählen. Hier wird zunächst die Anzahl der Zeichen der ersten Zeile ermittelt und die Indizes für die Textauswahl gesetzt. Als nächstes muss der sichtbare Textbereich ganz nach rechts verschoben werden. Zum Schluss muss der Textkursor an das Zeilenende bewegt werden.

class CTextBox : public CElement
  {
private:
   //--- Auswählen des ganzen Textes
   void              SelectAllText(void);
  };
//+------------------------------------------------------------------+
//| Auswählen des ganzen Textes                                      |
//+------------------------------------------------------------------+
void CTextBox::SelectAllText(void)
  {
//--- Abfrage der Größe des Arrays mit den Zeichen
   int symbols_total=::ArraySize(m_lines[0].m_symbol);
//--- Setzen des Index des ausgewählten Textes
   m_selected_line_from   =0;
   m_selected_line_to     =0;
   m_selected_symbol_from =0;
   m_selected_symbol_to   =symbols_total;
//--- Verschieben des Schiebereglers der horizontalen Bildlaufleiste an die letzte Position
   HorizontalScrolling();
//--- Verschieben des Kursor an das Ende der Zeile
   SetTextCursor(symbols_total,0);
  }

Das Eingabefeld erscheint nach einem Doppelklick auf eine Tabellenzelle. Aber um einen weiteren Klick zum Aktivieren des Textfeldes zu vermeiden, wird eine zusätzliche 'public' Methode CTextBox::ActivateTextBox() benötigt. Ein Aufruf simuliert einen Klick auf das Textfeld. Dazu wird einfach die Methode CTextBox::OnClickTextBox() aufgerufen und ihr der Namen des grafischen Objekts des Steuerelementes übergeben. Die Textauswahl erfolgt durch diese Methode.

class CTextBox : public CElement
  {
public:
   //--- Aktiviere das Textfeld
   void              ActivateTextBox(void);
  };
//+------------------------------------------------------------------+
//| Aktiviere das Textfeld                                           |
//+------------------------------------------------------------------+
void CTextBox::ActivateTextBox(void)
  {
   OnClickTextBox(m_textbox.Name());
  }

Da nur ein einziges Textfeld für die gesamte Tabelle verwendet wird, muss die Möglichkeit bestehen, die Größe der Tabelle ändern können, da die Zellen unterschiedliche Breiten haben können. Daher wurde eine zusätzliche 'public' Methode CTextBox::ChangeSize() eingeführt, die die zuvor implementierten Methoden aufruft, die in anderen Artikeln beschrieben wurden.

class CTextBox : public CElement
  {
public:
   //--- Größenänderung
   void              ChangeSize(const uint x_size,const uint y_size);
  };
//+------------------------------------------------------------------+
//| Größenänderung                                                   |
//+------------------------------------------------------------------+
void CTextBox::ChangeSize(const uint x_size,const uint y_size)
  {
//--- Festlegen der neuen Größe
   ChangeMainSize(x_size,y_size);
//--- Berechnen der Größe des Textfeldes
   CalculateTextBoxSize();
//--- Setzen der neuen Größe des Textfeldes
   ChangeTextBoxSize();
  }

Ein Doppelklick auf eine Zelle mit einem Textfeld ruft die Methode CTable::CheckPressedEdit() auf. Auch hier werden Klassenvariablen zum Speichern der Indizes der zuletzt bearbeiteten Zelle benötigt, um auf das Ereignis des Endes der Werteingabe (ON_END_EDIT) reagieren zu können. 

In der aktuellen Version wird das Texteingabefeld nur durch einen Doppelklick auf eine Zelle aufgerufen. Daher gibt es zu Beginn der Methode eine solche Prüfung. Anschließend werden die übergebenen Spalten- und Zeilenindizes gesichert. Um das Texteingabefeld korrekt über der Tabellenzelle zu platzieren, müssen die Koordinaten unter Berücksichtigung des Tabellenversatzes entlang der beiden Achsen berechnet werden. Zusätzlich wird das Vorhandensein von Überschriften in den Berechnungen berücksichtigt. Danach muss die Größe des Textfeldes berechnet, festgelegt und die aktuelle Zeichenfolge eingefügt werden, das in der Zelle angezeigt werden soll. Danach wird das Textfeld aktiviert, angezeigt, und das Diagramm neu gezeichnet, um die letzten Änderungen darzustellen.

class CTable : public CElement
  {
private:
   //--- Indices der Spalten und Zeilen der letzten, bearbeiteten Zellen
   int               m_last_edit_row_index;
   int               m_last_edit_column_index;
   //---
private:
   //--- Prüfen, ob der Klick auf eine Zelle mit einem Textfeld erfolgte
   bool              CheckPressedEdit(const int column_index,const int row_index,const bool double_click=false);
  };
//+------------------------------------------------------------------+
//| Prüfen, ob der Klick auf eine Zelle mit einem Textfeld erfolgte  |
//+------------------------------------------------------------------+
bool CTable::CheckPressedEdit(const int column_index,const int row_index,const bool double_click=false)
  {
//--- Verlassen, wenn es kein Doppelklick war
   if(!double_click)
      return(false);
//--- Speichern der Indices
   m_last_edit_row_index    =row_index;
   m_last_edit_column_index =column_index;
//--- Verschieben entlang der Achsen
   int x_offset=(int)m_table.GetInteger(OBJPROP_XOFFSET);
   int y_offset=(int)m_table.GetInteger(OBJPROP_YOFFSET);
//--- Setzen der neuen Koordinaten
   m_edit.XGap(m_columns[column_index].m_x-x_offset);
   m_edit.YGap(m_rows[row_index].m_y+((m_show_headers)? m_header_y_size : 0)-y_offset);
//--- Größe
   int x_size =m_columns[column_index].m_x2-m_columns[column_index].m_x+1;
   int y_size =m_cell_y_size+1;
//--- Setzen der Größe
   m_edit.GetTextBoxPointer().ChangeSize(x_size,y_size);
//--- Setzen der Werte aus der Tabellenzelle
   m_edit.SetValue(m_columns[column_index].m_rows[row_index].m_full_text);
//--- Aktiviere das Textfeld
   m_edit.GetTextBoxPointer().ActivateTextBox();
//--- Setzen des Fokus
   m_edit.GetTextBoxPointer().MouseFocus(true);
//--- Anzeigen des Textfeldes
   m_edit.Reset();
//--- Neuzeichnen des Charts
   m_chart.Redraw();
   return(true);
  }

Nach der Eingabe des Wertes in die Zelle wird ein Ereignis mit dem Identifikator ON_END_EDIT erzeugt, das in der Ereignisbehandlung der Tabelle empfangen werden muss. Die Methode CTable::OnEndEditCell() wurde implementiert, um auf diesen Vorgang zu reagieren. Sind Zellen mit Textfeldern vorhanden, und deren Identifikatoren stimmen überein, wird der neue Wert dieser Tabellenzelle gesetzt. Danach muss das Textfeld deaktiviert und ausgeblendet werden.

class CTable : public CElement
  {
private:
   //--- Bearbeitung des Endes der Werteeingabe in eine Zelle
   bool              OnEndEditCell(const int id);
  };
//+------------------------------------------------------------------+
//| Ereignisbehandlung                                               |
//+------------------------------------------------------------------+
void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   ...
//--- Bearbeitung des Endes der Werteeingabe
   if(id==CHARTEVENT_CUSTOM+ON_END_EDIT)
     {
      if(OnEndEditCell((int)lparam))
         return;
      //---
      return;
     }
   ...
  }
//+------------------------------------------------------------------+
//| Bearbeitung des Endes der Werteeingabe in eine Zelle             |
//+------------------------------------------------------------------+
bool CTable::OnEndEditCell(const int id)
  {
//--- Verlassen, wenn 1. die Identifikatoren nicht übereinstimmen oder 2. es keine Zellen mit Textfeldern gibt
   if(id!=CElementBase::Id() || !m_edit_state)
      return(false);
//--- Setzen des Wertes der Tabellenzelle
   SetValue(m_last_edit_column_index,m_last_edit_row_index,m_edit.GetValue(),0,true);
   Update();
//--- Deaktivieren und Ausblenden des Textfeldes
   m_edit.GetTextBoxPointer().DeactivateTextBox();
   m_edit.Hide();
   m_chart.Redraw();
   return(true);
  }

Ein Klick außerhalb des aktivierten Textfeldes sollte dieses Textfeld ausblenden. Dafür wird die Methode CTable::OnEndEditCell() benötigt. Zusätzlich muss das Textfeld deaktiviert werden, damit es beim nächsten Aufruf korrekt angezeigt wird. Die Methode CTable::OnEndEditCell() wird durch die Ereignisbehandlung der Tabelle aufgerufen, sobald das Ereignis eines geänderten Status der linken Maustaste (ON_CHANGE_MOUSE_LEFT_BUTTON) auftritt. Das gleiche Prinzip wird von der Methode CTable::CheckAndHideCombobox() verwendet, wenn es in der Zelle ein Kombinationsfeld gibt. Der Code dieser Methode wird hier nicht aufgeführt, da er mit dem bereits beschrieben praktisch ident ist.

class CTable : public CElement
  {
private:
   //--- Prüfen, ob das Steuerelement der Zelle ausgeblendet ist
   void              CheckAndHideEdit(void);
  };
//+------------------------------------------------------------------+
//| Ereignisbehandlung                                               |
//+------------------------------------------------------------------+
void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   ...
//--- Zustandsänderung der linken Maustaste
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE_MOUSE_LEFT_BUTTON)
     {
      ...
      //--- Prüfen, ob das Textfeld in der Zelle ausgeblendet ist
      CheckAndHideEdit();
      //--- Prüfen, ob das Kombinationsfeld in der Zelle ausgeblendet ist
      CheckAndHideCombobox();
      return;
     }
   ...
  }
//+------------------------------------------------------------------+
//| Prüfen, ob das Textfeld in der Zelle ausgeblendet ist            |
//+------------------------------------------------------------------+
void CTable::CheckAndHideEdit(void)
  {
//--- Verlassen, wenn es 1. kein Textfeld oder wenn es 2. ausgeblendet ist
   if(!m_edit_state || !m_edit.IsVisible())
      return;
//--- Prüfen des Fokus
   m_edit.GetTextBoxPointer().CheckMouseFocus();
//--- Deaktivieren und Ausblenden des Textfeldes, wenn 1. es außerhalb des Fokus ist und 1. die linke Maustaste gedrückt wurde
   if(!m_edit.GetTextBoxPointer().MouseFocus() && m_mouse.LeftButtonState())
     {
      m_edit.GetTextBoxPointer().DeactivateTextBox();
      m_edit.Hide();
      m_chart.Redraw();
     }
  }

Betrachten wir nun, wie die Methoden zum Aufruf des Kombinationsfeldes einer Tabellenzelle funktionieren. Für Zellen des Typs CELL_COMBOBOX wird ein Array zum Speichern der Werte der Kombinationsfeldliste sowie ein zusätzliche Variable zum Speichern des Index des ausgewählten Eintrags benötigt. Der Array und die Variable wurden der Struktur CTCell hinzugefügt.

class CTable : public CElement
  {
private:
   //--- Eigenschaften der Tabellenzellen
   struct CTCell
     {
      ...
      string            m_value_list[];   // Array of values (for cells with combo boxes)
      int               m_selected_item;  // Selected item in the combo box list
      ...
     };
  };

Wenn der Typ des Kombinationsfeldes (CELL_COMBOBOX) für eine Zelle in der benutzerdefinierten Klasse vor dem Anlegen der Tabelle angegeben wird, muss auch die Liste der Werte an die Kombinationsfeldliste übergeben werden.  

Das geschieht durch die Methode CTable::AddValueList(). Diese Methode wird auch der Zellenindex und der Index des in der Kombinationsfeldliste des gewählten Eintrags übergeben. Standardmäßig ist der erste Eintrag ausgewählt (Index 0). 

Am Anfang der Methode wird die Einhaltung der Arraygrenzen überprüft. Danach wird der Array in der Struktur CTCell auf die gleiche Größe wie das übergebene Array gesetzt und eine Kopie der Werte erstellt. Der Index des ausgewählte Elementes wird bei Überschreitung der Arraygrenzen angepasst und ebenfalls in der Struktur CTCell gespeichert. Der Text des markierten Elements wird in der Zelle gespeichert.

class CTable : public CElement
  {
public:
   //--- Hinzufügen einer Liste zum Kombinationsfeld
   void              AddValueList(const uint column_index,const uint row_index,const string &array[],const uint selected_item=0);
  };
//+------------------------------------------------------------------+
//| Hinzufügen einer Liste zum Kombinationsfeld                      |
//+------------------------------------------------------------------+
void CTable::AddValueList(const uint column_index,const uint row_index,const string &array[],const uint selected_item=0)
  {
//--- Prüfen der Arraygrenze
   if(!CheckOutOfRange(column_index,row_index))
      return;
//--- Setzen der Listengröße der angegebenen Zelle
   uint total=::ArraySize(array);
   ::ArrayResize(m_columns[column_index].m_rows[row_index].m_value_list,total);
//--- Speichen der übergebenen Werte
   ::ArrayCopy(m_columns[column_index].m_rows[row_index].m_value_list,array); 
//--- Prüfen des Index des gewählten Elementes in der Liste
   uint check_item_index=(selected_item>=total)? total-1 : selected_item;
//--- Speichern des gewählten Elementes in der Liste
   m_columns[column_index].m_rows[row_index].m_selected_item=(int)check_item_index;
//--- Speichern des Textes des gewählten Elementes in der Zelle
   m_columns[column_index].m_rows[row_index].m_full_text=array[check_item_index];
  }

Die Methode CTable::CheckPressedCombobox() bearbeitet den Doppelklick auf eine Zelle mit einem Kombinationsfeld. Hier werden die Indices der Zellen zunächst für die weitere Verarbeitung gespeichert, falls ein Listeneintrag selektiert wird. Anschließend werden die Koordinaten des Kombinationsfeldes relativ zur linken oberen Ecke der Zelle gesetzt. Danach werden die Steuerelemente auf die Größe der Zelle eingestellt. Um die Größe des Buttons (CButton) und der Liste (CListView) während der Laufzeit zu ändern, wurde die Methode ChangeSize() in ihre Klassen und zwei weitere Variablen aufgenommen. Da die Größe der Liste von Zelle zu Zelle variieren kann, muss die Liste jedes Mal neu aufgebaut und ausgefüllt werden. Anschließend werden die Elemente des Kombinationsfeldes neu gezeichnet und sichtbar gemacht. Ganz am Ende der Methode wird ein Ereignis über Änderungen des grafischen Interfaces erzeugt

class CTable : public CElement
  {
private:
   //--- Prüfen, ob Zelle mit Kombinationsfeld geklickt wurde
   bool              CheckPressedCombobox(const int column_index,const int row_index,const bool double_click=false);
  };
//+------------------------------------------------------------------+
//| Prüfen, ob Zelle mit Kombinationsfeld geklickt wurde             |
//+------------------------------------------------------------------+
bool CTable::CheckPressedCombobox(const int column_index,const int row_index,const bool double_click=false)
  {
//--- Verlassen, wenn es kein Doppelklick war
   if(!double_click)
      return(false);
//--- Speichern der Indices
   m_last_edit_row_index    =row_index;
   m_last_edit_column_index =column_index;
//--- Verschieben entlang der Achsen
   int x_offset=(int)m_table.GetInteger(OBJPROP_XOFFSET);
   int y_offset=(int)m_table.GetInteger(OBJPROP_YOFFSET);
//--- Setzen der neuen Koordinaten
   m_combobox.XGap(m_columns[column_index].m_x-x_offset);
   m_combobox.YGap(m_rows[row_index].m_y+((m_show_headers)? m_header_y_size : 0)-y_offset);
//--- Setzen der Tastengröße
   int x_size =m_columns[column_index].m_x2-m_columns[column_index].m_x+1;
   int y_size =m_cell_y_size+1;
   m_combobox.GetButtonPointer().ChangeSize(x_size,y_size);
//--- Setzen der Listengröße
   y_size=m_combobox.GetListViewPointer().YSize();
   m_combobox.GetListViewPointer().ChangeSize(x_size,y_size);
//--- Setzen der Größe der Zellliste
   int total=::ArraySize(m_columns[column_index].m_rows[row_index].m_value_list);
   m_combobox.GetListViewPointer().Rebuilding(total);
//--- Setzen der Liste der Zelle
   for(int i=0; i<total; i++)
      m_combobox.GetListViewPointer().SetValue(i,m_columns[column_index].m_rows[row_index].m_value_list[i]);
//--- Setzen des Elementes der Zelle
   int index=m_columns[column_index].m_rows[row_index].m_selected_item;
   m_combobox.SelectItem(index);
//--- Aktualisieren des Steuerelementes
   m_combobox.GetButtonPointer().MouseFocus(true);
   m_combobox.GetButtonPointer().Update(true);
   m_combobox.GetListViewPointer().Update(true);
//--- Anzeigen des Textfeldes
   m_combobox.Reset();
//--- Neuzeichnen des Charts
   m_chart.Redraw();
//--- Senden einer Nachricht über die Veränderung des grafischen Interfaces
   ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,"");
   return(true);
  }

Das Ereignis der Auswahl eines Elementes aus Kombinationsfeldliste (ON_CLICK_COMBOBOX_ITEM) wird von der Methode CTable::OnClickComboboxItem() bearbeitet. Hier wird zunächst geprüft, ob die Identifikatoren übereinstimmen und ob ein Kombinationsfeld in der Tabelle vorhanden ist. Sind diese Prüfungen abgeschlossen, werden der Index des ausgewählten Elementes und der Wert des Elementes in die Zelle entsprechend der vorher gespeicherten Index geschrieben.

class CTable : public CElement
  {
private:
   //--- Behandlung der Auswahl eines Elementes in der Liste der Zelle
   bool              OnClickComboboxItem(const int id);
  };
//+------------------------------------------------------------------+
//| Ereignisbehandlung                                               |
//+------------------------------------------------------------------+
void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   ...
//--- Auswählen eines Elementes in der Liste der Zelle
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
     {
      if(OnClickComboboxItem((int)lparam))
         return;
      //---
      return;
     }
   ...
  }
//+------------------------------------------------------------------+
//| Auswählen eines Elementes des Kombinationsfeldes der Zelle       |
//+------------------------------------------------------------------+
bool CTable::OnClickComboboxItem(const int id)
  {
//--- Verlassen, 1. wenn die Identifikator nicht übereinstimmen oder 2. wenn es keine Zellen mit Kombinationsfeldern gibt
   if(id!=CElementBase::Id() || !m_combobox_state)
      return(false);
//--- Indices der zuletzt bearbeiteten Zelle
   int c=m_last_edit_column_index;
   int r=m_last_edit_row_index;
//--- Sichern des Index des in der Zelle gewählten Elementes
   m_columns[c].m_rows[r].m_selected_item=m_combobox.GetListViewPointer().SelectedItemIndex();
//--- Setzen des Wertes der Tabellenzelle
   SetValue(c,r,m_combobox.GetValue(),0,true);
   Update();
   return(true);
  }

Am Ende wird dann alles so aussehen:

Fig. 6. Demonstration der Arbeit mit Textfeldern und Kombinationsfeldern in Tabellenzellen. 

Fig. 6. Demonstration der Arbeit mit Textfeldern und Kombinationsfeldern in Tabellenzellen.



Testanwendung

Zu Testzwecken wurde eine MQL-Anwendung mit der Tabelle (CTable) und dem mehrzeilige Textfeld (CTextBox) erstellt. In der ersten Spalte der Tabelle enthalten alle Zellen ein Ankreuzkästchen (CELL_CHECKBOX). In der zweiten Spalte der Tabelle haben die Zellen "Textfelder" (CELL_EDIT). In der dritten Spalte haben die Zellen abwechselnd "Kombinationsfelder" (CELL_COMBOBOX) und "Textfelder" (CELL_EDIT). In der fünften Spalte haben die Zellen Tasten (CELL_BUTTON). Die Ereignisbehandlung der benutzerdefinierten Klasse der MQL-Anwendung verarbeitet die Ereignisse und übergibt sie dem mehrzeiligen Textfeld zur Anzeige. 

Und so schaut alles aus:

 Fig. 7. MQL-Anwendung für die Prüfung der beschriebenen Aufgaben.


Fig. 7. MQL-Anwendung für die Prüfung der beschriebenen Aufgaben.


Diese Anwendung befinden sich im Archiv am Ende des Artikels für weitere Untersuchung.


Schlussfolgerung

Die Tabelle kann nun Zellen mit dem Typ "Textfeld" und "Kombinationsfeld" erzeugen. Die Anzeige des Steuerelementes jetzt mit der Maus in den Vollbildmodus vergrößert oder auf eine andere Größe manuell verändert werden.

Das allgemeine Schema der Bibliothek im gegenwärtigen Entwicklungsstadium:

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


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


Der vorgestellte Code ist kostenfrei. Sie können ihn in Ihren, auch kommerziellen, Projekten verwenden, damit Artikel schreiben und ihn für eigene Aufträge verwenden.

Wenn Sie Fragen zur Verwendung des Materials aus dem Artikel haben, können Sie diese im Kommentarteil stellen.


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

Beigefügte Dateien |
Tiefe neuronale Netzwerke (Teil II). Ausarbeitung und Auswahl von Prädiktoren Tiefe neuronale Netzwerke (Teil II). Ausarbeitung und Auswahl von Prädiktoren
Der zweite Artikel der Serie über tiefe neuronale Netze befasst sich mit der Ausarbeitung und Auswahl von Prädiktoren (= Variablen zur Wertevorhersage anderen Variablen) während des Prozesses der Datenaufbereitung für das Training eines Modells.
Erstellen und Testen benutzerdefinierter Symbole im MetaTrader 5 Erstellen und Testen benutzerdefinierter Symbole im MetaTrader 5
Das Erstellen von benutzerdefinierten Symbolen verschiebt die Grenzen der Entwicklung von Handelssystemen und der Finanzmarktanalyse. Jetzt können Händler Charts erstellen und Handelsstrategien mit einer unbegrenzten Anzahl von Finanzinstrumenten testen.
Tiefe neuronale Netzwerke (Teil III). Stichprobenauswahl und Verminderung der Dimensionen Tiefe neuronale Netzwerke (Teil III). Stichprobenauswahl und Verminderung der Dimensionen
Dieser Artikel ist eine Fortsetzung der Artikelreihe über tiefe neuronale Netze. Hierbei werden wir die Auswahl von Stichproben (Rauschunterdrückung), die Verminderung der Dimensionen der Eingangsdaten und die Aufteilung der Daten in die Datensätze train/val/test bei der Datenaufbereitung für das Training des neuronalen Netzes besprechen.
Tiefe neuronale Netzwerke (Teil I). Datenaufbereitung Tiefe neuronale Netzwerke (Teil I). Datenaufbereitung
Diese Artikelserie setzt das Thema "Tiefe neuronale Netzwerke" (DNN) fort, die in der letzten Zeit in vielen angewandten Bereichen einschließlich Trading verwendet werden. Es werden neue Themenbereiche betrachtet; anhand praktischer Experimente werden neue Methoden und Ideen geprüft. Der erste Artikel dieser Serie beschäftigt sich mit der Datenaufbereitung für DNN.