English Русский 中文 Español 日本語 Português
Grafische Interfaces X: mehrzeiliges Textfeld (build 8)

Grafische Interfaces X: mehrzeiliges Textfeld (build 8)

MetaTrader 5Beispiele | 16 März 2017, 09:41
919 3
Anatoli Kazharski
Anatoli Kazharski

Inhalt

 

Einführung

Um ein besseres Verständnis vom Zweck dieser Bibliothek zu erhalten, lesen Sie bitte den ersten Artikel: Grafische Interfaces I: Vorbereitung der Bibliotheksstruktur (Kapitel 1). Sie finden eine Liste von Artikeln mit Verweisen am Ende jeden Kapitels. Dort können Sie auch die komplette, aktuelle Version der Bibliothek zum derzeitigen Entwicklungsstand herunterladen. Die Dateien müssen im gleichen Verzeichnis wie das Archiv platziert werden.

Dieser Artikel beschreibt ein neues Element: das mehrzeilige Textfeld. Anders als bei Grafikobjekten des Typs OBJ_EDIT ist die vorgestellte Version nicht durch eine Anzahl von Buchstaben beschränkt. Sie bietet auch die Möglichkeiten eines einfachen Texteditors. Das heißt, Texte sind mehrzeilig, und der Kursor kann mit Maus oder Tasten bewegt werden. Gibt es mehr Zeilen als darstellbar sind, erscheint eine Bildlaufleiste. Das mehrzeilige Textfeld wird vollständig wiedergegeben, und dessen Qualität ist so nahe den Möglichkeiten des Betriebssystem wie möglich.


Tastengruppen und Tastaturlayout

Bevor wir uns dem Code von CTextBox (Textfeld) zuwenden, beschäftigen wir uns zunächst mit der Tastatur, da über sie die Daten eingegeben werden. Auch werden wir das Erkennen des Drückens einer Taste in der ersten Version der Klassenelemente behandeln. 

Die Tasten der Tastatur können in mehrere Gruppen unterteilt werden (siehe Fig. 1):

  • Kontrolltasten (orange)
  • Funktionstasten (pupur)
  • Alphanumerische Tasten (blau)
  • Navigationstasten (grün)
  • Numerische Tastatur (rot)

 Fig. 1. Tastengruppierung (Tastaturlayout QWERTY).

Fig. 1. Tastengruppierung (Tastaturlayout QWERTY).


Es gibt mehrere Latin-Tastaturlayouts für Englisch. Die bekannteste ist QWERTY. In unserem Fall, der russischen Sprache, verwenden wir das russische Layout - ЙЦУКЕН. QWERTY bleibt für Englisch und ist als zusätzliches Tastaturlayout definiert. 

Ab der Version build 1510, verfügt MQL über die Funktion ::TranslateKey(). Von ihr erhalten wir das Zeichen der gedrückten Taste, das durch die gewählte Sprache und durch das gewählte Tastaturlayout des Betriebssystem bestimmt ist. Früher mussten extra Zeichenarrays für jede Sprach erstellt werden, welches einen erheblichen, zusätzlichen Aufwand erforderte. Jetzt ist alles viel einfacher.


Umgang mit Tastendruck-Ereignissen

Das Auftreten eines Tastendrucks wird durch die Systemfunktion ::OnChartEvent() unter der Verwendung des Identifikators CHARTEVENT_KEYDOWN:

//+------------------------------------------------------------------+
//| Funktion eines Chart-Events                                      |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Tastendruck 
   if(id==CHARTEVENT_KEYDOWN)
     {
      ::Print("id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam,"; symbol: ",::ShortToString(::TranslateKey((int)lparam)));
      return;
     }
  }

Er wird mit den folgenden drei Parameter, der Funktion übergeben:

  • long Parameter (lparam) – Code der gedrückten Taste, d.h. der ASCII-Code des Zeichens oder der Kontrolltaste. 
  • dparam Parameter (dparam) – die Zahl der Tastendrücke wenn die Taste länger gedrückt wird. Der Wert ist immer gleich 1. Wird diese Zahl seit dem Beginn des Tastendrucks benötigt, wird sie unabhängig berechnet.
  • sparam Parameter (sparam) – Zeichenwert der Bit-Maske, die den Status der Tasten der Tastatur darstellt. Dieses Ereignis wird sofort bei einem Tastendruck ausgelöst. Wird die Taste gedrückt und gleich wieder losgelassen, wird hier der Scan-Code empfangen. Wird die Taste gedrückt und dann gehalten, wird ein Wert auf Basis des Scan-Codes + 16384 Bit gebildet.

Das Beispiel unten (Ausgabe im Log des Terminals) zeigt die Ergebnisse von Drücken und Loslassen der Tasten. Der Code der Taste ist 27(lparam), der Scan-Code zum Zeitpunkt des Drückens ist 1 (sparam), und wenn sie für ca. 500 Millisekunden gehalten wird, beginnt das Terminal einen Wert von 16385 (scan code + 16384 Bits).

2017.01.20 17:53:33.240 id: 0; lparam: 27; dparam: 1.0; sparam: 1
2017.01.20 17:53:33.739 id: 0; lparam: 27; dparam: 1.0; sparam: 16385
2017.01.20 17:53:33.772 id: 0; lparam: 27; dparam: 1.0; sparam: 16385
2017.01.20 17:53:33.805 id: 0; lparam: 27; dparam: 1.0; sparam: 16385
2017.01.20 17:53:33.837 id: 0; lparam: 27; dparam: 1.0; sparam: 16385
2017.01.20 17:53:33.870 id: 0; lparam: 27; dparam: 1.0; sparam: 16385
...

Nicht alle Tasten erzeugen den Identifikator CHARTEVENT_KEYDOWN. Einige werden direkt vom Terminal benötigt, andere lösen ganz einfach kein Ereignis aus. Im Bild unten sind sie blau markiert:

Fig. 2. Vom Terminal benötigte Tasten, die kein CHARTEVENT_KEYDOWN Ereignis auslösen. 

Fig. 2. Vom Terminal benötigte Tasten, die kein CHARTEVENT_KEYDOWN Ereignis auslösen.


ASCII-Codes der Buchstaben und die Kontrolltasten

Weitere Informationen dazu von Wikipedia (hier): 

Der "Amerikanischer Standard-Code für den Informationsaustausch" (ASCII) ist eine [standardmäßige] 7-Bit-Zeichenkodierung. Der ASCII-Code dient der Textdarstellung für die Telekommunikation, und in Computern und anderen Geräten. Als Standard wurde er 1963 erstmals veröffentlicht.

Das Bild unten zeigt die ASCII-Codes einer Tastatur:

 Fig. 3. ASCII-Codes der Zeichen und Kontrolltasten.

Fig. 3. ASCII-Codes der Tasten


Alle ASCII-Codes wurden in der Datei KeyCodes.mqh in Form einer Makrosubstitution (#define) platziert. Unten sehen Sie diesen Teil des Codes:

//+------------------------------------------------------------------+
//|                                                     KeyCodes.mqh |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| ASCII-Codes der Zeichen und Kontrolltasten                       |
//| Umgang mit Tastendrücken (long Parameter für das Ereignis)       |
//+------------------------------------------------------------------+
#define KEY_BACKSPACE          8
#define KEY_TAB                9
#define KEY_NUMPAD_5           12
#define KEY_ENTER              13
#define KEY_SHIFT              16
#define KEY_CTRL               17
#define KEY_BREAK              19
#define KEY_CAPS_LOCK          20
#define KEY_ESC                27
#define KEY_SPACE              32
#define KEY_PAGE_UP            33
#define KEY_PAGE_DOWN          34
#define KEY_END                35
#define KEY_HOME               36
#define KEY_LEFT               37
#define KEY_UP                 38
#define KEY_RIGHT              39
#define KEY_DOWN               40
#define KEY_INSERT             45
#define KEY_DELETE             46
...

 


Scancodes der Tasten

Weitere Informationen dazu von Wikipedia (hier):  

Ein Scancode ist in der Computertechnik eine Nummer, die von der Tastatur eines Rechners an diesen gesendet wird, wenn eine Taste gedrückt oder losgelassen wird. Eine Zahl, oder eine Reihe von Zahlen, wird jeder Taste einer Tastatur zugeordnet.

Das Bild unten zeigt die Scancodes der Tasten:

Fig. 4. Scancodes der Tasten. 

Fig. 4. Scancodes der Tasten.


So wie die ASCII-Codes sind auch die Scancodes in der Datei KeyCodes.mqh. Unten sehen Sie einen Teil dieser Liste:

//+------------------------------------------------------------------+
//|                                                     KeyCodes.mqh |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
...
//--- Bit
#define KEYSTATE_ON            16384
//+------------------------------------------------------------------+
//| Scancodes der Tasten (string Parameter des Ereignisses)          |
//+------------------------------------------------------------------+
//| Einmal gedrückt: KEYSTATE_XXX                                    |
//| Gedrückt gehalten: KEYSTATE_XXX + KEYSTATE_ON                    |
//+------------------------------------------------------------------+
#define KEYSTATE_ESC           1
#define KEYSTATE_1             2
#define KEYSTATE_2             3
#define KEYSTATE_3             4
#define KEYSTATE_4             5
#define KEYSTATE_5             6
#define KEYSTATE_6             7
#define KEYSTATE_7             8
#define KEYSTATE_8             9
#define KEYSTATE_9             10
#define KEYSTATE_0             11
//---
#define KEYSTATE_MINUS         12
#define KEYSTATE_EQUALS        13
#define KEYSTATE_BACKSPACE     14
#define KEYSTATE_TAB           15
//---
#define KEYSTATE_Q             16
#define KEYSTATE_W             17
#define KEYSTATE_E             18
#define KEYSTATE_R             19
#define KEYSTATE_T             20
#define KEYSTATE_Y             21
#define KEYSTATE_U             22
#define KEYSTATE_I             23
#define KEYSTATE_O             24
#define KEYSTATE_P             25
...

 


Hilfsklassen für die Tastatur

Für bequemeres Arbeiten mit der Tastatur wurde die Klasse CKeys implementiert. Sie befindet sich in der Datei Keys.mqh und lädt die Datei KeyCodes.mqh mit allen Tasten- und Zeichencodes. 

//+------------------------------------------------------------------+
//|                                                         Keys.mqh |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#include <EasyAndFastGUI\KeyCodes.mqh>
//+------------------------------------------------------------------+
//| Arbeitsklasse der Tastatur                                       |
//+------------------------------------------------------------------+
class CKeys
  {
public:
                     CKeys(void);
                    ~CKeys(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CKeys::CKeys(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CKeys::~CKeys(void)
  {
  }

Zur Erkennung eines Tastendrucks von:

(1) Alphanumerisches Zeichen (inklusive dem Leerzeichen)

(2) Numerische Tasten des Ziffernblocks

oder (3) Sonderzeichen,

verwenden wir die Methode CKeys::KeySymbol(). Er wird als Wert für den long-Parameter des Ereignisses mit dem Identifikator CHARTEVENT_KEYDOWN übergeben, und es wird ein Zeichen im string-Format oder eine leeres Zeichen ('') zurückgegeben, wenn die gedrückte Taste nicht in den speziellen Bereich fällt. 

class CKeys
  {
public:
   //--- Liefert das Zeichen der gedrückten Taste
   string            KeySymbol(const long key_code);
  };
//+------------------------------------------------------------------+
//| Liefert das Zeichen der gedrückten Taste                         |
//+------------------------------------------------------------------+
string CKeys::KeySymbol(const long key_code)
  {
   string key_symbol="";
//--- Falls ein Leerzeichen eingegeben wurde (Leertaste)
   if(key_code==KEY_SPACE)
     {
      key_symbol=" ";
     }
//--- Muss ein (1) Buchstabe, (2) eine Zahl des Ziffernblocks oder (3) ein Sonderzeichen eingegeben werden
   else if((key_code>=KEY_A && key_code<=KEY_Z) ||
           (key_code>=KEY_0 && key_code<=KEY_9) ||
           (key_code>=KEY_SEMICOLON && key_code<=KEY_SINGLE_QUOTE))
     {
      key_symbol=::ShortToString(::TranslateKey((int)key_code));
     }
//--- Rückgabe des Zeichens
   return(key_symbol);
  }

Jetzt benötigen wir noch eine Methode, um den Zustand der Kontrolltaste abzufragen. Sie wird in verschiedenen Situationen verwendet, in denen zwei Tasten gleichzeitig gedrückt werden und der Kursor im Textfeld bewegt wird.

Um den aktuellen Zustand der Kontrolltaste abzufragen, verwenden wir die Funktion ::TerminalInfoInteger(). Diese Funktion hat mehrere Identifikatoren, den aktuellen Zustand der Taste zu bestimmen. TERMINAL_KEYSTATE_CONTROL ist der Identifikator der Kontrolltaste. Alle anderen Identifikatoren Finden Sie in der Hilfe zur MQL5 Sprache.

So ist es mit diesen Identifikatoren ganz einfach festzustellen, ob eine Taste gedrückt wurde. Wird eine Taste gedrückt, wird ein Wert kleiner als Null zurückgeliefert

class CKeys
  {
public:
   //--- Rückgabe des Zustandes der Kontrolltaste
   bool              KeyCtrlState(void);
  };
//+------------------------------------------------------------------+
//| Rückgabe des Zustandes der Kontrolltaste                         |
//+------------------------------------------------------------------+
bool CKeys::KeyCtrlState(void)
  {
   return(::TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL)<0);
  }

Jetzt haben wir alles, um das Textfeld zu erstellen. 

 


Das mehrzeilige Textfeld.

Das mehrzeilige Textfeld kann auch in Kombination mit anderen Textfeldern verwendet werden. Es gehört zur Gruppe zusammengesetzter Textfelder, da es Bildlaufleisten enthält. Darüber hinaus kann das mehrzeilige Textfeld sowohl für die Eingabe wie für die Anzeige von vorher gesichertem Text verwendet werden.

Textfelder zur Eingabe numerischer Werte (die Klasse CSpinEdit) oder eigenen Texten (CTextEdit) sind bereits besprochen worden. Sie verwenden Grafikobjekte des Typs OBJ_EDIT. Sie haben eine gravierende Einschränkung: nur 63 Zeichen können eingegeben werden können, und es muss einzeilig sein. Daher war die gestellte Aufgabe, das Erstellen eines Textfeldes ohne solcher Einschränkungen.  


 

Fig. 5. Das mehrzeilige Textfeld.

Wenden wir uns nun der Klasse CTextBox zu, um das Textfeld zu erstellen.

 

Entwicklung der Klasse des Eingabefeldes

Wir erstellen eine Datei TextBox.mqh der Klasse CTextBox mit allen Standardmethoden für alle Textfelder der Bibliothek und laden sie folgende Dateien:

  • Mit den Basisklasse des Textfeldes — Element.mqh.
  • Mit der Klasse der Bildlaufleiste — Scrolls.mqh.
  • Mit der Klasse, die mit der Tastatur arbeitet — Keys.mqh.
  • Mit der Klasse des Zeitzählers — TimeCounter.mqh.
  • Mit der Klasse für die Darstellung auf dem Chart — Chart.mqh
//+------------------------------------------------------------------+
//|                                                      TextBox.mqh |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#include "Scrolls.mqh"
#include "..\Keys.mqh"
#include "..\Element.mqh"
#include "..\TimeCounter.mqh"
#include <Charts\Chart.mqh>
//+------------------------------------------------------------------+
//| Klasse zur Erstellung eines mehrzeiligen Textfeldes              |
//+------------------------------------------------------------------+
class CTextBox : public CElement
  {
private:
   //--- Instanz der Klasse für die Arbeit mit der Tastatur
   CKeys             m_keys;
   //--- Instanz der Klasse für das Chartmanagement
   CChart            m_chart;
   //--- Instanz der Klasse für die Arbeit mit der Zeitzähler
   CTimeCounter      m_counter;
   //---
public:
                     CTextBox(void);
                    ~CTextBox(void);
   //--- Chart Event Handler
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Timer
   virtual void      OnEventTimer(void);
   //--- Verschieben eines Elementes
   virtual void      Moving(const int x,const int y,const bool moving_mode=false);
   //--- (1) Zeigen, (2) Verbergen, (3) Rücksetzen, (4) Löschen
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) Einstellen, (2) Rücksetzen der Prioritäten der linken Maustaste
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
   //--- Löschen der Farbe
   virtual void      ResetColors(void) {}
   //---
private:
   //--- Ändern der Breite der rechten kante des Fensters
   virtual void      ChangeWidthByRightWindowSide(void);
   //--- Ändern der Höhe der Unterkante des Fensters
   virtual void      ChangeHeightByBottomWindowSide(void);
  };



Eigenschaften und Aussehen

Wir brauchen noch ein Struktur, wir nennen sie KeySymbolOptions, mit den Arrays der Zeichen und Eigenschaften. In der aktuellen Version verwenden wir zwei dynamische Arrays: 

  • In m_symbol[] werden alle Zeichen einzeln gespeichert.
  • In m_width[] wird die Breite der Zeichen einzeln gesichert.

Eine Instanz dieser Klasse wird auch als dynamischer Array deklariert. Dessen Größe entspricht der Anzahl der Zeilen im Textfeld.

class CTextBox : public CElement
  {
private:
   //--- Zeichen und ihre Eigenschaften
   struct KeySymbolOptions
     {
      string            m_symbol[]; // Zeichen
      int               m_width[];  // Breite der Zeichen
     };
   KeySymbolOptions  m_lines[];
  };

In der ersten Version dieses Textfeldes wird der Text in Form ganzer Zeilen ausgegeben. Deswegen muss die Zeile aus dem Array m_symbol[] erstellt werden. Die Methode CTextBox::CollectString() leistet diese Aufgabe, ihr muss dazu der Index der Zeile übergeben werden:

class CTextBox : public CElement
  {
private:
   //--- Die Variablen der Zeichenketten
   string            m_temp_input_string;
   //---
private:
   //--- Erstellen einer Zeichenkette aus den Einzelzeichen
   string            CollectString(const uint line_index);
  };
//+------------------------------------------------------------------+
//| Erstellen einer Zeichenkette aus den Einzelzeichen               |
//+------------------------------------------------------------------+
string CTextBox::CollectString(const uint line_index)
  {
   m_temp_input_string="";
   uint symbols_total=::ArraySize(m_lines[line_index].m_symbol);
   for(uint i=0; i<symbols_total; i++)
      ::StringAdd(m_temp_input_string,m_lines[line_index].m_symbol[i]);
//---
   return(m_temp_input_string);
  }

Als nächstes folgen die Eigenschaften des Textes im Textfeld, mit denen das Aussehen, ihr Zustand und Modus gestaltet werden kann:

  • Die Hintergrundfarbe in verschiedenen Zuständen
  • Textfarbe in verschiedenen Zuständen
  • Rahmenfarbe in verschiedenen Zuständen
  • Standardtext
  • Farbe des Standardtextes
  • Mehrzeilig
  • Nur-Lese Modus
class CTextBox : public CElement
  {
private:
   //--- Hintergrundfarbe
   color             m_area_color;
   color             m_area_color_locked;
   //--- Textfarbe
   color             m_text_color;
   color             m_text_color_locked;
   //--- Rahmenfarbe
   color             m_border_color;
   color             m_border_color_hover;
   color             m_border_color_locked;
   color             m_border_color_activated;
   //--- Standardtext
   string            m_default_text;
   //--- Farbe des Standardtextes
   color             m_default_text_color;
   //--- Mehrzeilig
   bool              m_multi_line_mode;
   //--- Nur-Lese Modus
   bool              m_read_only_mode;
   //---
public:
   //--- Hintergrundfarbe verschiedener Zustände
   void              AreaColor(const color clr)                { m_area_color=clr;                 }
   void              AreaColorLocked(const color clr)          { m_area_color_locked=clr;          }
   //--- Textfarbe verschiedener Zustände
   void              TextColor(const color clr)                { m_text_color=clr;                 }
   void              TextColorLocked(const color clr)          { m_text_color_locked=clr;          }
   //--- Rahmenfarbe verschiedener Zustände
   void              BorderColor(const color clr)              { m_border_color=clr;               }
   void              BorderColorHover(const color clr)         { m_border_color_hover=clr;         }
   void              BorderColorLocked(const color clr)        { m_border_color_locked=clr;        }
   void              BorderColorActivated(const color clr)     { m_border_color_activated=clr;     }
   //--- (1) Standardtext und (2) Farbe des Standardtextes
   void              DefaultText(const string text)            { m_default_text=text;              }
   void              DefaultTextColor(const color clr)         { m_default_text_color=clr;         }
   //--- (1) Mehrzeilig, (2) Nur-Lese Modus
   void              MultiLineMode(const bool mode)            { m_multi_line_mode=mode;           }
   bool              ReadOnlyMode(void)                  const { return(m_read_only_mode);         }
   void              ReadOnlyMode(const bool mode)             { m_read_only_mode=mode;            }
  };

Das Textfeld selbst (Hintergrund, Text, Rahmen und blinkenden Textkursor) wird vollständig als ein einziges Grafikobjekt des Typs OBJ_BITMAP_LABEL gezeichnet. Eigentlich ist es daher nur ein Bild. Es wird in zwei Fällen neu gezeichnet:

  • Bei Arbeiten mit dem Element
  • nach einer bestimmten Zeitspanne, bei einem blinkenden Kursor, wenn das Textfeld aktiviert wurde. 

Wenn der Mauskursor über dem Textfeld ist, ändert der Rahmen seine Farbe. Um unnötiges Neuzeichnen des Bildes zu verhindern, ist es notwendig den Moment abzupassen, wenn der Kursor den Rahmen des Textfeldes kreuzt. Dadurch wird das Textfeld nur einmal gezeichnet, dann nämlich, wenn der Kursor sich in das Textfeld oder aus dem Textfeld bewegt. Dafür wurden die Methoden CElementBase::IsMouseFocus() der Basisklasse dieses Textfeldes hinzugefügt. Sie lesen und setzen den Merker des Kreuzens:

//+------------------------------------------------------------------+
//| Base control class                                               |
//+------------------------------------------------------------------+
class CElementBase
  {
protected:
   //--- Zur Bestimmung des Augenblicks, da der Mauskursor den Rahmen des Textfeldes kreuzt
   bool              m_is_mouse_focus;
   //---
public:
   //--- Der Augenblick des Eindringens/Verlassens des Textfeldes
   bool              IsMouseFocus(void)                        const { return(m_is_mouse_focus);             }
   void              IsMouseFocus(const bool focus)                  { m_is_mouse_focus=focus;               }
  };

Um den Code einfach und lesbar zu halten, wurden zusätzliche, einfache Methoden ergänzt, die helfen, die Hintergrundfarbe des Textfeldes und Rahmen und Text relativ zum aktuellen Zustand des Textfeldes abzufragen: 

class CTextBox : public CElement
  {
private:
   //--- Abfrage der aktuellen Hintergrundfarbe
   uint              AreaColorCurrent(void);
   //--- Abfrage der aktuellen Textfarbe
   uint              TextColorCurrent(void);
   //--- Abfrage der aktuellen Rahmenfarbe
   uint              BorderColorCurrent(void);
  };
//+------------------------------------------------------------------+
//| Abfrage der aktuellen Hintergrundfarbe                           |
//+------------------------------------------------------------------+
uint CTextBox::AreaColorCurrent(void)
  {
   uint clr=::ColorToARGB((m_text_box_state)? m_area_color : m_area_color_locked);
//--- Rückgabe der Farbe
   return(clr);
  }
//+------------------------------------------------------------------+
//| Abfrage der aktuellen Textfarbe                                  |
//+------------------------------------------------------------------+
uint CTextBox::TextColorCurrent(void)
  {
   uint clr=::ColorToARGB((m_text_box_state)? m_text_color : m_text_color_locked);
//--- Rückgabe der Farbe
   return(clr);
  }
//+------------------------------------------------------------------+
//| Abfrage der aktuellen Rahmenfarbe                                |
//+------------------------------------------------------------------+
uint CTextBox::BorderColorCurrent(void)
  {
   uint clr=clrBlack;
//--- Ist das Element nicht gesperrt
   if(m_text_box_state)
     {
      //--- Ist das Feld aktiviert
      if(m_text_edit_state)
         clr=m_border_color_activated;
      //--- Ist es nicht aktiviert, prüfe den Fokus des Textfeldes
      else
         clr=(CElementBase::IsMouseFocus())? m_border_color_hover : m_border_color;
     }
//--- Ist das Textfeld blockiert
   else
      clr=m_border_color_locked;
//--- Rückgabe der Farbe
   return(::ColorToARGB(clr));
  }

Von vielen Methoden der Klasse wird die Zeilenhöhe des Textfeldes in Pixel in Relation zur gewählten Schriftart und dessen Größe benötigt. Dafür verwenden Sie die Methode CTextBox::LineHeight():

class CTextBox : public CElement
  {
private:
   //--- Abfrage der Zeilenhöhe
   uint              LineHeight(void);
  };
//+------------------------------------------------------------------+
//| Rückgabe der Zeilenhöhe                                          |
//+------------------------------------------------------------------+
uint CTextBox::LineHeight(void)
  {
//--- Bestimme die Schriftart, die im Textfeld verwendet werden soll (benötigt zur Bestimmung der Zeilenhöhe)
   m_canvas.FontSet(CElementBase::Font(),-CElementBase::FontSize()*10,FW_NORMAL);
//--- Rückgabe der Zeilenhöhe
   return(m_canvas.TextHeight("|"));
  }

Betrachten wir nun die Methoden des Zeichnens des Textfeldes. Beginnen wir mit der Methode CTextBox::DrawBorder() zum Zeichnen des Grenzen des Textfeldes. Ist die Gesamtgröße des Textfeldes größer als der sichtbare Teil, kann der sichtbare Bereich ausgeglichen werden (unter Verwendung der Bildlaufleiste oder des Kursors). Daher muss der Rahmen unter Berücksichtigung dieses Ausgleichs gezeichnet werden

class CTextBox : public CElement
  {
private:
   //--- Zeichnen des Rahmen
   void              DrawBorder(void);
  };
//+------------------------------------------------------------------+
//| Zeichnen des Rahmens des Textfeldes                              |
//+------------------------------------------------------------------+
void CTextBox::DrawBorder(void)
  {
//--- Abfrage der Rahmenfarbe relativ zum aktuellen Zustand des Textfeldes
   uint clr=BorderColorCurrent();
//--- Abfrage des Abstandes zur X-Achse
   int xo=(int)m_canvas.GetInteger(OBJPROP_XOFFSET);
   int yo=(int)m_canvas.GetInteger(OBJPROP_YOFFSET);
//--- Grenzen
   int x_size =m_canvas.X_Size()-1;
   int y_size =m_canvas.Y_Size()-1;
//--- Koordinaten: oben/rechts/unten/links
   int x1[4]; x1[0]=x;         x1[1]=x_size+xo; x1[2]=xo;        x1[3]=x;
   int y1[4]; y1[0]=y;         y1[1]=y;         y1[2]=y_size+yo; y1[3]=y;
   int x2[4]; x2[0]=x_size+xo; x2[1]=x_size+xo; x2[2]=x_size+xo; x2[3]=x;
   int y2[4]; y2[0]=y;         y2[1]=y_size+yo; y2[2]=y_size+yo; y2[3]=y_size+yo;
//--- Zeichnen des Rahmens mit den Koordinaten
   for(int i=0; i<4; i++)
      m_canvas.Line(x1[i],y1[i],x2[i],y2[i],clr);
  }

Die Methode CTextBox::DrawBorder() wird auch innerhalb der Methode CTextBox::ChangeObjectsColor() verwendet, wenn der Mauskursor über dem Textfeld ist und sich die Rahmenfarbe ändern soll (siehe im Code unten). Das zu erreichen, wird einfach der Rahmen neu gezeichnet (und nicht das ganze Feld) und das Bild aktualisiert. Die Methode CTextBox::ChangeObjectsColor() wird innerhalb des Event Handlers des Textfeldes aufgerufen. Das ist der Ort, an dem das Kreuzen der Feldgrenzen durch den Mauskursors erkannt wird, um zu häufiges Neuzeichnen zu vermeiden.

class CTextBox : public CElement
  {
private:
   //--- Ändern der Farben eines Objektes
   void              ChangeObjectsColor(void);
  };
//+------------------------------------------------------------------+
//| Ändern der Farben eines Objektes                                 |
//+------------------------------------------------------------------+
void CTextBox::ChangeObjectsColor(void)
  {
//--- Falls nicht im Fokus
   if(!CElementBase::MouseFocus())
     {
      //--- Falls noch nicht bekannt, dass es noch nicht im Fokus ist
      if(CElementBase::IsMouseFocus())
        {
         //--- Setzen des Merkers
         CElementBase::IsMouseFocus(false);
         //--- Ändern der Farbe
         DrawBorder();
         m_canvas.Update();
        }
     }
   else
     {
      //--- Falls noch nicht bekannt, dass es im Fokus ist
      if(!CElementBase::IsMouseFocus())
        {
         //--- Setzen des Merkers
         CElementBase::IsMouseFocus(true);
         //--- Ändern der Farbe
         DrawBorder();
         m_canvas.Update();
        }
     }
  }

Die Methode CTextBox::TextOut() soll Text auf einem Hintergrund ausgeben. Ganz zu Anfang wird der Hintergrund durch das Füllen mit der übergebenen Farbe gelöscht. Danach gibt es für das Programm zwei Möglichkeiten:

  • Ist eine mehrzeilige Darstellung deaktiviert, und hat die Zeile zugleich keine Zeichen, wird der Standardtext angezeigt (wenn angegeben). Er wird in der Mitte des Textfeldes angezeigt.
  • Ist eine mehrzeilige Darstellung deaktiviert, und hat die Zeile mindestens ein Zeichen, wird die Höhe der Zeile abgefragt und alle Zeilen werde mittels einer Schleife, aus dem Array der Zeichen zusammengesetzt und dargestellt. Die Abstände des Textes von der linken, oberen Ecke des Textfeldes sind standardmäßig vorgegeben. Diese sind 5 Pixel von der X-Achse und 4 Pixel von der Y-Achse. Diese Werte können mit der Methode CTextBox::TextXOffset() und CTextBox::TextYOffset() überschrieben werden. 
class CTextBox : public CElement
  {
private:
   //--- Der Textabstand von den Kanten des Textfeldes
   int               m_text_x_offset;
   int               m_text_y_offset;
   //---
public:
   //--- Der Textabstand von den Kanten des Textfeldes
   void              TextXOffset(const int x_offset)           { m_text_x_offset=x_offset;         }
   void              TextYOffset(const int y_offset)           { m_text_y_offset=y_offset;         }
   //---
private:
   //--- Ausgabe des Textes auf dem Hintergrund
   void              TextOut(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTextBox::CTextBox(void) : m_text_x_offset(5),
                           m_text_y_offset(4)
  {
...
  }
//+------------------------------------------------------------------+
//| Ausgabe des Textes auf dem Hintergrund                           |
//+------------------------------------------------------------------+
void CTextBox::TextOut(void)
  {
//--- Löschen des Hintergrundes
   m_canvas.Erase(AreaColorCurrent());
//--- Abfrage der Größe des Arrays mit den Zeichen
   uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
//--- Wenn mehrzeilig oder die Anzahl der Zeichen ist größer Null
   if(m_multi_line_mode || symbols_total>0)
     {
      //--- Abfrage der Zeilenhöhe
      int line_height=(int)LineHeight();
      //--- Abfrage der Größe des Zeilenarrays
      uint lines_total=::ArraySize(m_lines);
      //---
      for(uint i=0; i<lines_total; i++)
        {
         //--- Abfrage der Koordinaten für den Text
         int x=m_text_x_offset;
         int y=m_text_y_offset+((int)i*line_height);
         //--- Zusammensetzen der Zeichenkette aus dem Zeichenarray
         CollectString(i);
         //--- Textausgabe
         m_canvas.TextOut(x,y,m_temp_input_string,TextColorCurrent(),TA_LEFT);
        }
     }
//--- Wenn nicht mehrzeilig und keine Zeichen angegeben, wird der Standardtext angezeigt
   else
     {
      //--- Textausabe, wenn angegeben
      if(m_default_text!="")
         m_canvas.TextOut(m_area_x_size/2,m_area_y_size/2,m_default_text,::ColorToARGB(m_default_text_color),TA_CENTER|TA_VCENTER);
     }
  }

Für das Zeichnen des Textkursors müssen wir die Koordinaten ausrechnen. Für die Berechnung der X-Koordinate müssen wir die Indices der Zeile und des Zeichens kennen, wo der Kursor platziert werden soll. Das erledigt die Methode CTextBox::LineWidth(): Da die Breite jedes Zeichens im dynamischen Array m_width[] der Struktur KeySymbolOptions gespeichert wurde, bleibt nur die Zeichenbreiten an der angegebenen Stelle aufzusummieren

class CTextBox : public CElement
  {
private:
   //--- Rückgabe der Zeilenbreite in Pixel
   uint              LineWidth(const uint line_index,const uint symbol_index);
  };
//+------------------------------------------------------------------+
//| Liefert die Zeilenbreite vom Beginn bis zur angegebenen Position |
//+------------------------------------------------------------------+
uint CTextBox::LineWidth(const uint line_index,const uint symbol_index)
  {
//--- Abfrage der Größe des Zeilenarrays
   uint lines_total=::ArraySize(m_lines);
//--- Verhinderung des Überschreitens der Arraygröße
   uint l=(line_index<lines_total)? line_index : lines_total-1;
//--- Abfrage der Größe des Zeichenarrays der angegebenen Zeile
   uint symbols_total=::ArraySize(m_lines[l].m_width);
//--- Verhinderung des Überschreitens der Arraygröße
   uint s=(symbol_index<symbols_total)? symbol_index : symbols_total;
//--- Aufsummieren der Breite aller Zeichen
   uint width=0;
   for(uint i=0; i<s; i++)
      width+=m_lines[l].m_width[i];
//--- Rückgabe der Zeilenbreite
   return(width);
  }

Die Methoden zur Abfrage der Koordinaten des Textkursors sind ganz einfach (siehe den Code unten). Die Koordinaten sind in den Variablen m_text_cursor_x und m_text_cursor_y gesichert. Die Berechnung der Koordinaten verwendet die aktuelle Position des Kursors sowie die Indices der Zeile und des Zeichens, wohin der Kursor bewegt werden soll. Die Variablen m_text_cursor_x_pos und m_text_cursor_y_pos sollen diese Werte aufnehmen.

class CTextBox : public CElement
  {
private:
   //--- Die aktuellen Koordinaten des Textkursors
   int               m_text_cursor_x;
   int               m_text_cursor_y;
   //--- Die aktuelle Position des Textkursors
   uint              m_text_cursor_x_pos;
   uint              m_text_cursor_y_pos;
   //---
private:
   //--- Berechnung der Koordinaten des Textkursors
   void              CalculateTextCursorX(void);
   void              CalculateTextCursorY(void);
  };
//+------------------------------------------------------------------+
//| Berechnung der X-Koordinate des Textkursors                      |
//+------------------------------------------------------------------+
void CTextBox::CalculateTextCursorX(void)
  {
//--- Abfrage der Zeilenbreite
   int line_width=(int)LineWidth(m_text_cursor_x_pos,m_text_cursor_y_pos);
//--- Berechnen und Sichern der X-Koordinate der Zelle
   m_text_cursor_x=m_text_x_offset+line_width;
  }
//+------------------------------------------------------------------+
//| Berechnung der Y-Koordinate des Textkursors                      |
//+------------------------------------------------------------------+
void CTextBox::CalculateTextCursorY(void)
  {
//--- Abfrage der Zeilenhöhe
   int line_height=(int)LineHeight();
//--- Abfrage der Y-Koordinate des Textkursors
   m_text_cursor_y=m_text_y_offset+int(line_height*m_text_cursor_y_pos);
  }

Alles ist für die Umsetzung der Methode CTextBox::DrawCursor() zum Zeichnen des Textkursors bereit. In vielen anderen Texteditoren, kann festgestellt werden, dass der Textkursor teilweise über die Pixel anderer Zeichen reicht. Man sieht, dass der Textkursor sie nicht einfach überdeckt. Die überdeckten Pixel der Zeichen erscheinen in anderer Farbe. Das geschieht, um die Lesbarkeit der Zeichen zu erhalten. 

Der Screenshot zum Beispiel zeigt ein 'd' und 'д', die jeweils einmal vom Kursor überdeckt und einmal nicht überdeckt werden.

 Fig. 6. Beispiel des Textkursor das Zeichen 'd' überdeckend.

Fig. 6. Beispiel des Textkursor das Zeichen 'd' überdeckend.

 Fig. 7. Beispiel des Textkursor das Zeichen 'д' überdeckend

Fig. 7. Beispiel des Textkursor das Zeichen 'д' überdeckend


Damit der Kursor und das überdeckte Zeichen zu jeder Zeit vor jeder Hintergrundfarbe erkennbar bleiben, genügt es die Farbe der überlappten Pixel zu invertieren. 

Betrachten wir nun die Methode CTextBox::DrawCursor(), die den Textkursor zeichnet. Die Breite des Kursors beträgt ein Pixel, und seine Höhe passt zur Zeilenhöhe. Zu Beginn holen wir uns die X-Koordinate und die Zeilenhöhe, an der der Kursor gezeichnet werden soll. Die Y-Koordinate berechnet sich in einer Schleife, da sie Pixel für Pixel gezeichnet wird. Erinnern wir uns, die Klasse CColors für das Arbeiten mit Farben wurde bereits vorher in der Basisklasse des Textfeldes deklariert. Daher erhalten wir die aktuelle Farbe des Pixels an einer bestimmten Stelle durch die Iteration nach der Berechnung der Y-Koordinate. Dann verwenden wir die Methode CColors::Negative(), um die Farbe zu invertieren und sie wieder am selben Ort zu platzieren

class CTextBox : public CElement
  {
private:
   //--- Zeichnen des Textkursors
   void              DrawCursor(void);
  };
//+------------------------------------------------------------------+
//| Zeichnen des Textkursors                                         |
//+------------------------------------------------------------------+
void CTextBox::DrawCursor(void)
  {
//--- Abfrage der Zeilenhöhe
   int line_height=(int)LineHeight();
//--- Abfrage der Y-Koordinate des Textkursors
   CalculateTextCursorX();
//--- Zeichnen des Textkursors
   for(int i=0; i<line_height; i++)
     {
      //--- Abfrage der Y-Koordinate des Pixels
      int y=m_text_y_offset+((int)m_text_cursor_y_pos*line_height)+i;
      //--- Abfrage der aktuellen Farbe des Pixels
      uint pixel_color=m_canvas.PixelGet(m_text_cursor_x,y);
      //--- Invertieren der Farbe des Kursors
      pixel_color=m_clr.Negative((color)pixel_color);
      m_canvas.PixelSet(m_text_cursor_x,y,::ColorToARGB(pixel_color));
     }
  }

Jetzt haben wir die beiden Methoden CTextBox::DrawText() und CTextBox::DrawTextAndCursor(), um das Textfeld mit einem Kursor zu zeichnen. 

The CTextBox::DrawText() method is to be used when it is only necessary to update the text in an inactive text box. Alles ist einfach hier. Ist das Textfeld nicht ausgeblendet, wird das Textfeld gezeigt, der Rahmen gezeichnet und das Bild aktualisiert.  

class CTextBox : public CElement
  {
private:
   //--- Textausgabe
   void              DrawText(void);
  };
//+------------------------------------------------------------------+
//| Textausgabe                                                      |
//+------------------------------------------------------------------+
void CTextBox::DrawText(void)
  {
//--- Verlassen, wenn ausgeblendet
   if(!CElementBase::IsVisible())
      return;
//--- Ausgabe des Textes
   CTextBox::TextOut();
//--- Zeichnen des Rahmens
   DrawBorder();
//--- Aktualisieren des Textfeldes
   m_canvas.Update();
  }

Ist das Textfeld aktiviert, ist es notwendig, zusätzlich zum Text einen blinkenden Kursor zu zeigen - mit der Methode CTextBox::DrawTextAndCursor(). Für das Blinken müssen wir den aktuellen Status des Kursors, gezeigt/ausgeblendet, kennen. Jedes mal, wenn diese Methode aufgerufen wird, wird dieser Status umgekehrt. Sie bietet zusätzlich die Möglichkeit, die Anzeige dann zu erzwingen, wenn der Wert true (das Argument show_state) der Methode übergeben wird. Diese unbedingte Anzeige ist für das Bewegen des Kursor im aktivierten Textfeld nötig. Tatsächlich wird das Blinken durch einen Timer des Textfeldes kontrolliert, mit einer Zeitspanne, die im Konstruktor des Timers bestimmt wird. Ihr Wert beträgt 200 Millisekunden. Der Zähler muss zurückgesetzt werden, jedes mal beim Aufruf der Methode CTextBox::DrawTextAndCursor(). 

class CTextBox : public CElement
  {
private:
   //--- Anzeige des Textes und des blinkenden Kursors
   void              DrawTextAndCursor(const bool show_state=false);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTextBox::CTextBox(void)
  {
//--- Einstellungen des Zeitzähler
   m_counter.SetParameters(16,200);
  }
//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CTextBox::OnEventTimer(void)
  {
...
//--- Pause zwischen den Aktualisierungen des Kursors
   if(m_counter.CheckTimeCounter())
     {
      //--- Aktualisierung des Textkursors, wenn das Textfeld sichtbar und aktiviert ist
      if(CElementBase::IsVisible() && m_text_edit_state)
         DrawTextAndCursor();
     }
  }
//+------------------------------------------------------------------+
//| Anzeige des Textes und des blinkenden Kursors                    |
//+------------------------------------------------------------------+
void CTextBox::DrawTextAndCursor(const bool show_state=false)
  {
//--- Statusbestimmung des Textkursors (angezeigt/ausgeblendet)
   static bool state=false;
   state=(!show_state)? !state : show_state;
//--- Ausgabe des Textes
   CTextBox::TextOut();
//--- Zeichnen des Textkursors
   if(state)
      DrawCursor();
//--- Zeichnen des Rahmens
   DrawBorder();
//--- Aktualisieren des Textfeldes
   m_canvas.Update();
//--- Zählerrücksetzung
   m_counter.ZeroTimeCounter();
  }

Um ein mehrzeiliges Textfeld zu erstellen, benötigen wir drei private Methoden, zwei für die Bildlaufleisten und eine public Methode für externe Aufrufe aus der eigenen Klasse: 

class CTextBox : public CElement
  {
private:
   //--- Objekte zum Erstellen des Elementes
   CRectCanvas       m_canvas;
   CScrollV          m_scrollv;
   CScrollH          m_scrollh;
   //---
public:
   //--- Methoden zum Erstellen des Textfeldes
   bool              CreateTextBox(const long chart_id,const int subwin,const int x_gap,const int y_gap);
   //---
private:
   bool              CreateCanvas(void);
   bool              CreateScrollV(void);
   bool              CreateScrollH(void);
   //---
public:
   //--- Rückgabe der Zeiger auf die Bildlaufleisten
   CScrollV         *GetScrollVPointer(void)                   { return(::GetPointer(m_scrollv));  }
   CScrollH         *GetScrollHPointer(void)                   { return(::GetPointer(m_scrollh));  }
  };

Vor dem Aufruf der Methode CTextBox::CreateCanvas(), die den Hintergrund erstellt, müssen wir dessen Größe berechnen. Eine Methode ähnlich der zur Anpassung von Tabellen mit dem Typ CCanvasTable werden wir anwenden. Skizzieren wir sie kurz. Es gibt die Größe des ganzen Bildes und die des sichtbaren Teils. Die Größe des Textfeldes ist gleich der des sichtbaren Teils. Werden der Textkursor oder die Bildlaufleisten bewegt, ändern sich die Koordinaten des Bildes, während die Koordinaten des sichtbaren Teils (das sind auch die Koordinaten des Textfeldes) unverändert bleiben.

Die Größe bezüglich der Y-Achse ist ganz einfach das Produkt aus der Anzahl der Zeilen und ihrer Höhe. Es werden auch der Abstand von den Kanten des Textfeldes und die Größe der Bildlaufleiste hier berücksichtigt. Für die Berechnung der Größe bezüglich der X-Achse, benötigen wir die längste Zeile des ganzen Arrays. Das wird von der Methode CTextBox::MaxLineWidth() durchgeführt. Hier wird über alle Zeilen in einer Schleife iteriert und jede Zeilenlänge, die größer als alle vorherigen ist, wird gesichert und am Ende zurückgegeben.

class CTextBox : public CElement
  {
private:
   //--- Rückgabe der Zeilenlänge
   uint              MaxLineWidth(void);
  };
//+------------------------------------------------------------------+
//| Rückgabe der größten Zeilenlänge                                 |
//+------------------------------------------------------------------+
uint CTextBox::MaxLineWidth(void)
  {
   uint max_line_width=0;
//--- Abfrage der Größe des Zeilenarrays
   uint lines_total=::ArraySize(m_lines);
   for(uint i=0; i<lines_total; i++)
     {
      //--- Abfrage der Größe des Arrays mit den Zeichen
      uint symbols_total=::ArraySize(m_lines[i].m_symbol);
      //--- Abfrage der Zeilenbreite
      uint line_width=LineWidth(symbols_total,i);
      //--- Sichern der größten Länge
      if(line_width>max_line_width)
         max_line_width=line_width;
     }
//--- Rückgabe der größten Zeilenlänge
   return(max_line_width);
  }

Die Methode CTextBox::CalculateTextBoxSize() zur Berechnung der Größe des Textfeldes ist unten aufgeführt. Sie wird von den Methoden CTextBox::ChangeWidthByRightWindowSide() und CTextBox::ChangeHeightByBottomWindowSide() aufgerufen. Ihr Zweck ist die automatischen Anpassung der Größe des Textfeldes, wenn die entsprechenden Eigenschaften durch den Entwickler definiert wurden. 

class CTextBox : public CElement
  {
private:
   //--- Gesamtgröße und die Größe des sichtbaren Teils des Textfeldes
   int               m_area_x_size;
   int               m_area_y_size;
   int               m_area_visible_x_size;
   int               m_area_visible_y_size;
   //---
private:
   //--- Berechnung der Breite des Textfeldes
   void              CalculateTextBoxSize(void);
  };
//+------------------------------------------------------------------+
//| Berechnung der Breite des Textfeldes                             |
//+------------------------------------------------------------------+
void CTextBox::CalculateTextBoxSize(void)
  {
//--- Ermittele die längste Zeile des Textfeldes
   int max_line_width=int((m_text_x_offset*2)+MaxLineWidth()+m_scrollv.ScrollWidth());
//--- Bestimme die Gesamtbreite
   m_area_x_size=(max_line_width>m_x_size)? max_line_width : m_x_size;
//--- Bestimme die sichtbare Breite
   m_area_visible_x_size=m_x_size;
//--- Abfrage der Zeilenhöhe
   int line_height=(int)LineHeight();
//--- Abfrage der Größe des Zeilenarrays
   int lines_total=::ArraySize(m_lines);
//--- Berechne die Gesamthöhe des Textfeldes
   int lines_height=int((m_text_y_offset*2)+(line_height*lines_total)+m_scrollh.ScrollWidth());
//--- Ermittle die Gesamthöhe
   m_area_y_size=(m_multi_line_mode && lines_height>m_y_size)? lines_height : m_y_size;
//--- Ermittle die sichtbare Höhe
   m_area_visible_y_size=m_y_size;
  }

Alle Größen wurden berechnet. Jetzt müssen sie angewendet werden. Das erledigt die Methode CTextBox::ChangeTextBoxSize(). Ihre Argumente werden falls nötig spezifiziert, um den sichtbaren Teil an den Anfang zusetzen oder am augenblicklichen Ort zu belassen. Weiters ändert diese Methode die Bildlaufleiste und stellt die endgültige Größe des sichtbaren Teils relativ zu den Schieberegler her. Der Code dieser Methoden wird nicht weiter besprochen, da er in früheren Artikeln bereits besprochen wurde. 

class CTextBox : public CElement
  {
private:
   //--- Größenänderung des Textfeldes
   void              ChangeTextBoxSize(const bool x_offset=false,const bool y_offset=false);
  };
//+------------------------------------------------------------------+
//| Größenänderung des Textfeldes                                    |
//+------------------------------------------------------------------+
void CTextBox::ChangeTextBoxSize(const bool is_x_offset=false,const bool is_y_offset=false)
  {
//--- Größenanpassung der Tabelle
   m_canvas.XSize(m_area_x_size);
   m_canvas.YSize(m_area_y_size);
   m_canvas.Resize(m_area_x_size,m_area_y_size);
//--- Setzen der Größe des sichtbaren Teils
   m_canvas.SetInteger(OBJPROP_XSIZE,m_area_visible_x_size);
   m_canvas.SetInteger(OBJPROP_YSIZE,m_area_visible_y_size);
//--- Differenz zwischen der Gesamtbreite und des sichtbaren Teils
   int x_different=m_area_x_size-m_area_visible_x_size;
   int y_different=m_area_y_size-m_area_visible_y_size;
//--- Setzen des Rahmenabstandes im Bild für die X und die Y-Achse
   int x_offset=(int)m_canvas.GetInteger(OBJPROP_XOFFSET);
   int y_offset=(int)m_canvas.GetInteger(OBJPROP_YOFFSET);
   m_canvas.SetInteger(OBJPROP_XOFFSET,(!is_x_offset)? 0 : (x_offset<=x_different)? x_offset : x_different);
   m_canvas.SetInteger(OBJPROP_YOFFSET,(!is_y_offset)? 0 : (y_offset<=y_different)? y_offset : y_different);
//--- Anpassung der Bildlaufleisten
   ChangeScrollsSize();
//--- Anpassen der Daten
   ShiftData();
  }

Die folgenden Variablen und Methoden dienen zur Abfrage und Kontrolle des Zustandes des Textfeldes:

  • Die Methode CTextBox::TextEditState() fragt den Zustand des Textfeldes ab. 
  • Der Aufruf der Methode CTextBox::TextBoxState() sperrt/entsperrt das Textfeld. Ein gesperrtes Textfeld wird in den Nur-Lese-Zustand überführt. Die Farben von Hintergrund, Rahmen und Text werden entsprechenden gesetzt (das kann durch den Nutzer bereits vor dem Erstellen des Textfeldes geschehen). 
class CTextBox : public CElement
  {
private:
   //--- Nur-Lese Modus
   bool              m_read_only_mode;
   //--- Änderbarkeit des Textfeldes
   bool              m_text_edit_state;
   //--- Zustand des Textfeldes
   bool              m_text_box_state;
   //---
public:
   //--- (1) Änderbarkeit des Textfeldes, (2) Abfragen/Setzen des Zustandes des Textfeldes
   bool              TextEditState(void)                 const { return(m_text_edit_state);        }
   bool              TextBoxState(void)                  const { return(m_text_box_state);         }
   void              TextBoxState(const bool state);
  };
//+------------------------------------------------------------------+
//| Setzen des Zustandes des Textfeldes                              |
//+------------------------------------------------------------------+
void CTextBox::TextBoxState(const bool state)
  {
   m_text_box_state=state;
//--- Setzen relativ zum augenblicklichen Zustand
   if(!m_text_box_state)
     {
      //--- Prioritäten
      m_canvas.Z_Order(-1);
      //--- Das änderbare Textfeld in Nur-Lese-Zustand
      m_read_only_mode=true;
     }
   else
     {
      //--- Prioritäten
      m_canvas.Z_Order(m_text_edit_zorder);
      //--- Das änderbare Textfeld im Editiermodus
      m_read_only_mode=false;
     }
//--- Aktualisieren des Textfeldes
   DrawText();
  }

 


Handhabung des Textkursors

Das editierbare Textfeld wird durch eine Klick aktiviert. Sofort werden die Koordinaten des Ortes des Klicks festgestellt und der Textkursor an diese Stelle gesetzt. Das erledigt die Methode CTextBox::OnClickTextBox(). Aber bevor wir sie beschreiben, wenden wir uns einigen Hilfsmethoden zu, die nicht nur von ihr, sondern auch von vielen anderen der Klasse CTextBox aufgerufen werden.

Die Methode CTextBox::SetTextCursor(), die die Werte der Position des Textkursors aktualisiert. Im einzeiligen Modus ist die Position der Y-Achse immer 0.

class CTextBox : public CElement
  {
private:
   //--- Die aktuelle Position des Textkursors
   uint              m_text_cursor_x_pos;
   uint              m_text_cursor_y_pos;
   //---
private:
   //--- Setzen des Kursor auf die angegeben Position
   void              SetTextCursor(const uint x_pos,const uint y_pos);
  };
//+------------------------------------------------------------------+
//| Setzen des Kursor auf die angegeben Position                     |
//+------------------------------------------------------------------+
void CTextBox::SetTextCursor(const uint x_pos,const uint y_pos)
  {
   m_text_cursor_x_pos=x_pos;
   m_text_cursor_y_pos=(!m_multi_line_mode)? 0 : y_pos;
  }

Methoden der Steuerung der Bildlaufleisten. Ähnliche Methoden sind bereits in früheren Artikeln dieser Serie beschrieben worden, daher wird der Code hier nicht aufgeführt. Nur zur Erinnerung: Wird eine Parameter nicht übergeben, wird der Schieberegler auf die letzte Position bewegt, also an das Ende der Liste, des Textes oder Dokumentes. 

class CTextBox : public CElement
  {
public:
   //--- In einer Tabelle blättern: (1) vertikal und (2) horizontal
   void              VerticalScrolling(const int pos=WRONG_VALUE);
   void              HorizontalScrolling(const int pos=WRONG_VALUE);
  };

CTextBox::DeactivateTextBox() wird für das Deaktivieren des Textfeldes benötigt. Ein neue Funktion, von den Entwicklern zur Verfügung gestellt, sollte nicht unerwähnt bleiben. Ein zusätzlicher Chart-Name (CHART_KEYBOARD_CONTROL) wurde der Enumeration ENUM_CHART_PROPERTY hinzugefügt. Durch sie kann der Chart durch die Tasten 'Left', 'Right', 'Home', 'End', 'Page Up', 'Page Down' und '+' and '-' zum Vergrößern/Verkleinern verändert werden. Daher müssen, wenn das Textfeld aktiviert ist, diese Chartfunktionen unterdrückt werden, damit diese Tasten das Bearbeiten des Textfeldes nicht stören oder unterbrechen. Wird das Textfeld deaktiviert, muss daher diese Funktion der Tasten wiederhergestellt werden. 

Hier ist es notwendig, das Textfeld neu zu zeichnen, und im nicht-mehrzeiligen Modus den Textkursor und die Schieberegler der Bildlaufleisten an den Anfang der Zeile zu setzen. 

class CTextBox : public CElement
  {
private:
   //--- Deaktivieren des Textfeldes
   void              DeactivateTextBox(void);
  };
//+------------------------------------------------------------------+
//| Deaktivieren des Textfeldes                                      |
//+------------------------------------------------------------------+
void CTextBox::DeactivateTextBox(void)
  {
//--- Verlassen, wenn bereits deaktiviert
   if(!m_text_edit_state)
      return;
//--- Deaktivieren
   m_text_edit_state=false;
//--- Aktivieren der Chart-Steuerung
   m_chart.SetInteger(CHART_KEYBOARD_CONTROL,true);
//--- Textausgabe
   DrawText();
//--- Wenn der Mehrzeilenmodus deaktiviert ist
   if(!m_multi_line_mode)
     {
      //--- Setze den Kursor an den Anfang der Zeile
      SetTextCursor(0,0);
      //--- Setze die Bildlaufleiste an den Anfang der Zeile
      HorizontalScrolling(0);
     }
  }

Während der Kontrolle des Textkursors ist es notwendig abzufangen, wenn er die Grenzen des sichtbaren Teils überschreitet. Wenn das passiert, muss der Kursor wieder im sichtbaren Teil platziert werden. Auch dafür werden weitere, wiederverwendbare Methoden benötigt. Die erlaubten Grenzen des Textfeldes müssen unter Berücksichtigung des mehrzeiligen Modus und der Existenz der Bildlaufleiste berechnet werden. 

Um zu berechnen, um wie viel der sichtbare Teil verschoben werden muss, muss der aktuelle Abstand als erstes ermittelt werden: 

class CTextBox : public CElement
  {
private:
   //--- Zur Berechnung der Grenzen des sichtbaren Teils des Textfeldes
   int               m_x_limit;
   int               m_y_limit;
   int               m_x2_limit;
   int               m_y2_limit;
   //---
private:
   //--- Berechnung der Grenzen des Textfeldes
   void              CalculateBoundaries(void);
   void              CalculateXBoundaries(void);
   void              CalculateYBoundaries(void);
  };
//+------------------------------------------------------------------+
//| Berechnung der Grenzen des Textfeldes für beide Achsen           |
//+------------------------------------------------------------------+
void CTextBox::CalculateBoundaries(void)
  {
   CalculateXBoundaries();
   CalculateYBoundaries();
  }
//+------------------------------------------------------------------+
//| Berechnung der Grenzen des Textfeldes der X-Achse                |
//+------------------------------------------------------------------+
void CTextBox::CalculateXBoundaries(void)
  {
//--- Abfrage der X-Koordinate und des Abstandes der X-Achse
   int x       =(int)m_canvas.GetInteger(OBJPROP_XDISTANCE);
   int xoffset =(int)m_canvas.GetInteger(OBJPROP_XOFFSET);
//--- Zur Berechnung der Grenzen des sichtbaren Teils des Textfeldes
   m_x_limit  =(x+xoffset)-x;
   m_x2_limit =(m_multi_line_mode)? (x+xoffset+m_x_size-m_scrollv.ScrollWidth()-m_text_x_offset)-x : (x+xoffset+m_x_size-m_text_x_offset)-x;
  }
//+------------------------------------------------------------------+
//| Berechnung der Grenzen des Textfeldes der Y-Achse                |
//+------------------------------------------------------------------+
void CTextBox::CalculateYBoundaries(void)
  {
//--- Verlassen, wenn der mehrzeilige Modus nicht aktiv ist
   if(!m_multi_line_mode)
      return;
//--- Abfrage der Y-Koordinate und des Abstandes der Y-Achse
   int y       =(int)m_canvas.GetInteger(OBJPROP_YDISTANCE);
   int yoffset =(int)m_canvas.GetInteger(OBJPROP_YOFFSET);
//--- Zur Berechnung der Grenzen des sichtbaren Teils des Textfeldes
   m_y_limit  =(y+yoffset)-y;
   m_y2_limit =(y+yoffset+m_y_size-m_scrollh.ScrollWidth())-y;
  }

Zur genauen Berechnung der Position der Bildlaufleiste relativ zur aktuellen Position des Kursors, verwenden wir die folgende Funktion: 

class CTextBox : public CElement
  {
private:
   //--- Berechnung der X-Position des Schiebereglers der Bildlaufleiste des linken Randes des Textfeldes
   int               CalculateScrollThumbX(void);
   //--- Berechnung der X-Position des Schiebereglers der Bildlaufleiste des rechten Randes des Textfeldes
   int               CalculateScrollThumbX2(void);
   //--- Berechnung der Y-Position des Schiebereglers der Bildlaufleiste des oberen Randes des Textfeldes
   int               CalculateScrollThumbY(void);
   //--- Berechnung der Y-Position des Schiebereglers der Bildlaufleiste des unteren Randes des Textfeldes
   int               CalculateScrollThumbY2(void);
  };
//+------------------------------------------------------------------+
//| Berechnung der X-Position der Bildlaufleiste, linke Seite        |
//+------------------------------------------------------------------+
int CTextBox::CalculateScrollThumbX(void)
  {
   return(m_text_cursor_x-m_text_x_offset);
  }
//+------------------------------------------------------------------+
//| Berechnung der X-Position der Bildlaufleiste, rechte Seite       |
//+------------------------------------------------------------------+
int CTextBox::CalculateScrollThumbX2(void)
  {
   return((m_multi_line_mode)? m_text_cursor_x-m_x_size+m_scrollv.ScrollWidth()+m_text_x_offset : m_text_cursor_x-m_x_size+m_text_x_offset*2);
  }
//+------------------------------------------------------------------+
//| Berechnung der Y-Position der Bildlaufleiste, obere Kante        |
//+------------------------------------------------------------------+
int CTextBox::CalculateScrollThumbY(void)
  {
   return(m_text_cursor_y-m_text_y_offset);
  }
//+------------------------------------------------------------------+
//| Berechnung der Y-Position der Bildlaufleiste, untere Kante       |
//+------------------------------------------------------------------+
int CTextBox::CalculateScrollThumbY2(void)
  {
//--- Bestimme die Schriftart, die im Textfeld verwendet werden soll (benötigt zur Bestimmung der Zeilenhöhe)
   m_canvas.FontSet(CElementBase::Font(),-CElementBase::FontSize()*10,FW_NORMAL);
//--- Abfrage der Zeilenhöhe
   int line_height=m_canvas.TextHeight("|");
//--- Berechnung und Rückgabe des Wertes
   return(m_text_cursor_y-m_y_size+m_scrollh.ScrollWidth()+m_text_y_offset+line_height);
  }

Gestalten wir es so, dass ein Klick auf das Textfeld ein Ereignis hervorruft, das explizit anzeigt, dass das Textfeld aktiviert wurde. Wie benötigen auch das Ereignis des im Textfeld bewegten Kursors. Wir fügen neue Bezeichner der Datei Defines.mqh hinzu: 

  • ON_CLICK_TEXT_BOX Aktivierung des Textfeldes.
  • ON_MOVE_TEXT_CURSOR Bewegung des Textkursors. 
//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
...
#define ON_CLICK_TEXT_BOX          (31) // Aktivierung des Textfeldes
#define ON_MOVE_TEXT_CURSOR        (32) // Bewegung des Textkursors

Die augenblickliche Position des Textkursors wird als eine Zeichenkette für eine zusätzliche Information durch diese beiden Bezeichner gesichert. Das existiert bereits in vielen anderen Texteditoren, auch im MetaEditor. Der Screenshot unten zeigt ein Beispiel einer erstellten Zeichenkette, um sie in der Statuszeile des Codeeditors anzuzeigen.


 

Fig. 8. Position des Textkursor im MetaEditor.

Unten ist der Code der Methode CTextBox::TextCursorInfo(), die eine Zeichenkette, so wie im Screenshot oben, zurückgibt. Ebenso sehen Sie weitere Methoden, die die Anzahl der Zeilen und die der Zeichen der angegebenen Zeile, so wie die aktuelle Position des Textkursors zurückgibt. 

class CTextBox : public CElement
  {
private:
   //--- Rückgabe des Index (1) der Zeile und (2) des Zeichens neben dem Kursor,
   //    (3) die Anzahl der Zeilen, (4) die Anzahl der Zeichen in der angegebenen Zeile
   uint              TextCursorLine(void)                      { return(m_text_cursor_y_pos);      }
   uint              TextCursorColumn(void)                    { return(m_text_cursor_x_pos);      }
   uint              LinesTotal(void)                          { return(::ArraySize(m_lines));     }
   uint              ColumnsTotal(const uint line_index);
   //--- Information über den Textkursor (Zeile/Zeilenzahl, Spalte/Spaltenzahl)
   string            TextCursorInfo(void);
  };
//+------------------------------------------------------------------+
//| Rückgabe der Zeichenzahl in der angegebenen Zeile                |
//+------------------------------------------------------------------+
uint CTextBox::ColumnsTotal(const uint line_index)
  {
//--- Abfrage der Größe des Zeilenarrays
   uint lines_total=::ArraySize(m_lines);
//--- Verhinderung des Überschreitens der Arraygröße
   uint check_index=(line_index<lines_total)? line_index : lines_total-1;
//--- Abfrage der Größe des Arrays der Zeichen der Zeile
   uint symbols_total=::ArraySize(m_lines[check_index].m_symbol);
//--- Rückgabe der Zeichenzahl
   return(symbols_total);
  }
//+------------------------------------------------------------------+
//| Information über den Textkursor                                  |
//+------------------------------------------------------------------+
string CTextBox::TextCursorInfo(void)
  {
//--- Komponenten der Zeichenkette
   string lines_total        =(string)LinesTotal();
   string columns_total      =(string)ColumnsTotal(TextCursorLine());
   string text_cursor_line   =string(TextCursorLine()+1);
   string text_cursor_column =string(TextCursorColumn()+1);
//--- Erstelle die Zeichenkette
   string text_box_info="Ln "+text_cursor_line+"/"+lines_total+", "+"Col "+text_cursor_column+"/"+columns_total;
//--- Rückgabe der Zeichenkette
   return(text_box_info);
  }

Jetzt haben wir alles beisammen, um die Methode CTextBox::OnClickTextBox() beschreiben zu können, die zu Anfang dieses Kapitel erwähnt wurde (siehe den Code unten). Hier wird ganz am Anfang der Name des Objektes geprüft, das mit der linken Maustaste geklickt wurde. Stellt sich heraus, der Klick erfolgte nicht auf dem Textfeld, wird eine Nachricht gesendet, dass die Bearbeitung beendet wurde (ON_END_EDIT Ereigniskennung), falls das Textfeld noch aktiv sein sollte. Danach wird das Textfeld deaktiviert und die Methode verlassen.

War aber der Klick auf dem Textfeld, erfolgen zwei weitere Prüfungen. Das Programm wird verlassen, wenn der Nur-Lese-Modus aktiviert ist, oder wenn das Textfeld blockiert ist. Ist eine der beiden Prüfungen falsch, wird mit dem Hauptteil der Methode fortgesetzt.

Zu allererst wird die Chartbearbeitung mit der Tastatur ausgeschaltet. Dann erfragen wir (1) den aktuellen Abstand des sichtbaren Teils des Textfeldes, und bestimmen (2) die relativen Koordinaten des Punktes, an dem der Klick passierte. Die Berechnung in der Hauptschleife der Methode benötigt ebenfalls die Zeilenhöhe. 

Suchen wir zuerst die Zeile des Mausklicks in diesem Teil. Die Suche des Zeichens beginnt erst, wenn die Y -Koordinate des Klicks zwischen der oberen und unteren Grenze der Zeile. Falls es sich ergibt, dass diese Linie keine Zeichen enthält, wird der Kursor und die horizontale Bildlaufleiste an den Anfang der Zeile gesetzt. Damit beendet sich die Schleife.

Gibt es in der Zeile Buchstaben, beginnt die nächste Schleife, in der wir das Zeichen suchen, das geklickt wurde. Das Prinzip der Suche entspricht praktisch der der Zeilen. Der einzig Unterschied ist, dass die Zeichenbreite bei jeder Iteration abgefragt wird, da die Zeichen verschiedener Schrifttypen nicht immer die gleiche Breite haben. Wurde das geklickte Zeichen gefunden, wird der Textkursor gesetzt und die Suche beendet. Wurde das Zeichnen in dieser Zeile nicht gefunden und das letzte Zeichen erreicht, stellen wir den Kursor an die letzte Stelle dieser Zeile und beenden die Suche. 

Als nächstes, im mehrzeiligen Modus, muss überprüft werde, ob der Textkursor (zumindest teilweise) die Grenzen des sichtbaren Teils auf der Y-Achse überschreitet. Ist das der Fall, wird der sichtbare Teil relativ zur Position des Textkursors angepasst. Danach wird der Merker des Textfeldes aktiviert und es neu gezeichnet

Ganz am Ende der Methode CTextBox::OnClickTextBox() wird ein Ereignis erzeugt, das anzeigt, dass das Textfeld aktiviert wurde (ON_CLICK_TEXT_BOX Ereigniskennung). Um eine eindeutige Identifikation zu garantieren, wird (1) die Kennung und (2) der Index des Textfeldes und — zusätzlich — (3) eine Informationen über die Position des Kursors gesendet. 

class CTextBox : public CElement
  {
private:
   //--- Handhabung eines festgehaltenen Elementes
   bool              OnClickTextBox(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Handhabung eines Klicks auf das Textfeld                         |
//+------------------------------------------------------------------+
bool CTextBox::OnClickTextBox(const string clicked_object)
  {
//--- Verlassen, wenn es ein anderer Objektname ist
   if(m_canvas.Name()!=clicked_object)
     {
      //--- Sende eine Nachricht über das Ende der Bearbeitung des Textfeldes, falls es aktiviert ist
      if(m_text_edit_state)
         ::EventChartCustom(m_chart_id,ON_END_EDIT,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
      //--- Deaktivieren des Textfeldes
      DeactivateTextBox();
      return(false);
     }
//--- Verlassen, wenn (1) Nur-Lese-Modus aktiviert ist oder (2) das Textfeld blockiert ist
   if(m_read_only_mode || !m_text_box_state)
      return(true);
//--- Deaktivieren der Chartbearbeitung
   m_chart.SetInteger(CHART_KEYBOARD_CONTROL,false);
//--- Abfrage des Abstandes der X- und Y-Achse
   int xoffset=(int)m_canvas.GetInteger(OBJPROP_XOFFSET);
   int yoffset=(int)m_canvas.GetInteger(OBJPROP_YOFFSET);
//--- Bestimme die Koordinaten des Textfeldes unter dem Mauskursor
   int x =m_mouse.X()-m_canvas.X()+xoffset;
   int y =m_mouse.Y()-m_canvas.Y()+yoffset;
//--- Abfrage der Zeilenhöhe
   int line_height=(int)LineHeight();
//--- Abfrage der Größe des Zeilenarrays
   uint lines_total=::ArraySize(m_lines);
//--- Bestimme das geklickte Zeichen
   for(uint l=0; l<lines_total; l++)
     {
      //--- Setze die ersten Koordinaten für die Überprüfungen
      int x_offset=m_text_x_offset;
      int y_offset=m_text_y_offset+((int)l*line_height);
      //--- Zustandsprüfung bezüglich der Y-Achse
      bool y_pos_check=(l<lines_total-1)?(y>=y_offset && y<y_offset+line_height) : y>=y_offset;
      //--- Wenn der Klick nicht in dieser Zeile erfolgte, weiter
      if(!y_pos_check)
         continue;
      //--- Abfrage der Größe des Arrays mit den Zeichen
      uint symbols_total=::ArraySize(m_lines[l].m_width);
      //--- Ist dies eine Leerzeile, setze den Kursor auf die angegebene Position und verlasse die Schleife
      if(symbols_total<1)
        {
         SetTextCursor(0,l);
         HorizontalScrolling(0);
         break;
        }
      //--- Finde das geklickte Zeichen
      for(uint s=0; s<symbols_total; s++)
        {
         //--- Wurde das Zeichen gefunden, platziere den Kursor dort und verlasse die Schleife
         if(x>=x_offset && x<x_offset+m_lines[l].m_width[s])
           {
            SetTextCursor(s,l);
            l=lines_total;
            break;
           }
         //--- Summiere die Zeichenbreite für die folgende Prüfung 
         x_offset+=m_lines[l].m_width[s];
         //--- Ist es das letzte Zeichen, setze den Kursor ans Zeilenende und verlasse die Schleife
         if(s==symbols_total-1 && x>x_offset)
           {
            SetTextCursor(s+1,l);
            l=lines_total;
            break;
           }
        }
     }
//--- Ist der mehrzeilige Modus aktiv
   if(m_multi_line_mode)
     {
      //--- Hole die Grenzen des sichtbaren Teils des Textfeldes
      CalculateYBoundaries();
      //--- Abfrage der Y-Koordinate des Textkursors
      CalculateTextCursorY();
      //--- Bewege die Bildlaufleiste, wenn der Textkursor den sichtbaren Teil verlässt
      if(m_text_cursor_y<=m_y_limit)
         VerticalScrolling(CalculateScrollThumbY());
      else
        {
         if(m_text_cursor_y+(int)LineHeight()>=m_y2_limit)
            VerticalScrolling(CalculateScrollThumbY2());
        }
     }
//--- Aktiviere das Textfeld
   m_text_edit_state=true;
//--- Aktualisiere Text und Kursor
   DrawTextAndCursor(true);
//--- Senden einer Nachricht darüber
   ::EventChartCustom(m_chart_id,ON_CLICK_TEXT_BOX,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
   return(true);
  }



Zeichen eingeben

Wir betrachten jetzt die Methode CTextBox::OnPressedKey(). Sie behandelt Tastendrücke und, wenn der Tastendruck einem Buchstaben gilt, muss er der Zeile hinzugefügt werden, dort wo sich der Kursor befindet. Weitere Methoden werden benötigt, um den Array der Struktur KeySymbolOptions zu vergrößern, und den in das Textfeld eingegebene Zeichen dem Array hinzuzufügen, so wie die Breite dieses Zeichens.

Eine recht einfach Methode CTextBox::ArraysResize() wird für die Größenänderung des Arrays in verschiedenen Methoden der Klasse CTextBox verwendet:

class CTextBox : public CElement
  {
private:
   //--- Größenänderung de Arrays der spezifischen Zeile
   void              ArraysResize(const uint line_index,const uint new_size);
  };
//+------------------------------------------------------------------+
//| Größenänderung de Arrays der spezifischen Zeile                  |
//+------------------------------------------------------------------+
void CTextBox::ArraysResize(const uint line_index,const uint new_size)
  {
//--- Abfrage der Größe des Zeilenarrays
   uint lines_total=::ArraySize(m_lines);
//--- Verhinderung des Überschreitens der Arraygröße
   uint l=(line_index<lines_total)? line_index : lines_total-1;
//--- Setze die Arraygröße in der Struktur
   ::ArrayResize(m_lines[line_index].m_width,new_size);
   ::ArrayResize(m_lines[line_index].m_symbol,new_size);
  }

Die Methode CTextBox::AddSymbol() fügt das neue Zeichen des Textfeldes ein. Betrachten wir sie genauer. Beim Einfügen eines neuen Zeichens, muss die Arraygröße um ein Element erhöht werden. Die aktuelle Position des Textkursor kann an jeder Stelle der Zeichenkette stehen. Daher müssen, bevor ein Zeichen dem Array hinzugefügt werden kann, zunächst alle Zeichen ab der Position des Textkursor um eins nach rechts verschoben werden. Danach kann das eingegebene Zeichen an der Position des Textkursors eingetragen werden. Am Ende der Methode wird der Textkursor dann noch um eins nach rechts verschoben

class CTextBox : public CElement
  {
private:
   //--- Fügt eine Zeichen und seine Breite in die Arrays der Struktur
   void              AddSymbol(const string key_symbol);
  };
//+------------------------------------------------------------------+
//| Fügt eine Zeichen und seine Breite in die Arrays der Struktur    |
//+------------------------------------------------------------------+
void CTextBox::AddSymbol(const string key_symbol)
  {
//--- Abfrage der Größe des Arrays mit den Zeichen
   uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
//--- Größenänderung des Arrays
   ArraysResize(m_text_cursor_y_pos,symbols_total+1);
//--- Schiebe alle Zeichen vom Ende des Arrays bis zum Index des neuen Zeichens
   for(uint i=symbols_total; i>m_text_cursor_x_pos; i--)
     {
      m_lines[m_text_cursor_y_pos].m_symbol[i] =m_lines[m_text_cursor_y_pos].m_symbol[i-1];
      m_lines[m_text_cursor_y_pos].m_width[i]  =m_lines[m_text_cursor_y_pos].m_width[i-1];
     }
//--- Abfrage der Breite des Zeichens
   int width=m_canvas.TextWidth(key_symbol);
//--- Einfügen des Zeichens in die leere Stelle
   m_lines[m_text_cursor_y_pos].m_symbol[m_text_cursor_x_pos] =key_symbol;
   m_lines[m_text_cursor_y_pos].m_width[m_text_cursor_x_pos]  =width;
//--- Erhöhen des Positionszählers des Kursors
   m_text_cursor_x_pos++;
  }

Der Code unten zeigt die Methode CTextBox::OnPressedKey(). Ist das Textfeld aktiv, denn versuchen wir das Zeichen, das als Tastencodes der Methode übergeben wurde, zu bekommen. Wenn die gedrückte Taste kein Zeichen hat, verlässt das Programm die Methode. Gibt es aber ein Zeichen, dann wird es dem Array gemäß den Eigenschaften hinzugefügt. Das Textfeld könnte durch das Einfügen eines Zeichens seine Größe ändern, es müssen daher diese Werte neu berechnet werden. Danach erfragen wir die Grenzen des Textfeldes und die augenblickliche Position des Textkursors. Ist der Kursor jenseits der rechten Kante des Textfeldes, muss die Position des Schiebereglers der horizontalen Bildlaufleiste angepasst werden. Danach wird das Textfeld mit einer unbedingten (true) Anzeige des Kursors neu gezeichnet. Ganz am Ende der Methode CTextBox::OnPressedKey() wird ein Ereignis über die Bewegung des Textkursors (ON_MOVE_TEXT_CURSOR) mit dem Identifikator des Textfeldes, dem Index des Elementes und zusätzlichen Informationen über die Position des Kursors. 

class CTextBox : public CElement
  {
private:
   //--- Umgang mit einem Tastendruck
   bool              OnPressedKey(const long key_code);
  };
//+------------------------------------------------------------------+
//| Umgang mit einem Tastendruck                                     |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKey(const long key_code)
  {
//--- Verlassen des Textfeldes, wenn es nicht aktiv ist
   if(!m_text_edit_state)
      return(false);
//--- Abfrage des Tastencodes
   string pressed_key=m_keys.KeySymbol(key_code);
//--- Verlassen, wenn es kein Zeichen gibt
   if(pressed_key=="")
      return(false);
//--- Einfügen des Zeichens mit seinen Eigenschaften
   AddSymbol(pressed_key);
//--- Berechne die Größe des Textfeldes
   CalculateTextBoxSize();
//--- Setze die neue Größe des Textfeldes
   ChangeTextBoxSize(true,true);
//--- Hole die Grenzen des sichtbaren Teils des Textfeldes
   CalculateXBoundaries();
//--- Abfrage der Y-Koordinate des Textkursors
   CalculateTextCursorX();
//--- Bewege die Bildlaufleiste, wenn der Textkursor den sichtbaren Teil verlässt
   if(m_text_cursor_x>=m_x2_limit)
      HorizontalScrolling(CalculateScrollThumbX2());
//--- Aktualisieren des Textes des Textfeldes
   DrawTextAndCursor(true);
//--- Senden einer Nachricht darüber
   ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
   return(true);
  }

 


Die 'Backspace'-Taste

Betrachten wir nun die Situation, wenn durch drücken der 'Backspace'-Taste ein Zeichen gelöscht wird. In diesem Fall ruft der Event Handler des mehrzeiligen Textfeldes die Methode CTextBox::OnPressedKeyBackspace() auf. Sie benötigt weitere Methoden, die bisher nicht beschrieben wurden. Stellen wir zuerst deren Code vor.

Zeichen werden mit der Methode CTextBox::DeleteSymbol() gelöscht. Zu Beginn wird überprüft, ob es mindestens ein Zeichen in der Zeile gibt. Wenn nicht, wird der Textkursor an den Anfang der Zeile gestellt, und die Methode wird verlassen. Gibt es aber welche, wird die Position des vorgehenden Zeichens abgefragt. Dies ist der Index des ersten Zeichens derer, die um eins nach rechts verschoben werden. Danach wird auch der Textkursor um eins nach links bewegt. Am Ende der Methode wird die Größe des Arrays um eins vermindert.

class CTextBox : public CElement
  {
private:
   //--- Lösche ein Zeichen
   void              DeleteSymbol(void);
  };
//+------------------------------------------------------------------+
//| Lösche ein Zeichen                                               |
//+------------------------------------------------------------------+
void CTextBox::DeleteSymbol(void)
  {
//--- Abfrage der Größe des Arrays mit den Zeichen
   uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
//--- Ist der Array leer
   if(symbols_total<1)
     {
      //--- Setze den Kursor auf Position Null in dieser Zeile
      SetTextCursor(0,m_text_cursor_y_pos);
      return;
     }
//--- Abfrage der Position des vorhergehenden Zeichens
   int check_pos=(int)m_text_cursor_x_pos-1;
//--- Verlassen, wenn jenseits der Grenzen
   if(check_pos<0)
      return;
//--- Verschiebe alle Zeichen um eins nach links ab dem Index des gelöschten Zeichens bis zum Ende des Arrays
   for(uint i=check_pos; i<symbols_total-1; i++)
     {
      m_lines[m_text_cursor_y_pos].m_symbol[i] =m_lines[m_text_cursor_y_pos].m_symbol[i+1];
      m_lines[m_text_cursor_y_pos].m_width[i]  =m_lines[m_text_cursor_y_pos].m_width[i+1];
     }
//--- Verringern des Positionszähler des Kursor um eins
   m_text_cursor_x_pos--;
//--- Größenänderung des Arrays
   ArraysResize(m_text_cursor_y_pos,symbols_total-1);
  }

Befindet sich der Textkursor am Zeilenanfang, und es ist nicht die erste Zeile, muss die aktuelle Zeile gelöscht werden, und die anderen Zeilen müssen um eins nach oben verschoben werden. Gibt es Zeichen in der zu löschenden Zeile, müssen diese der Zeile über ihr angehängt werden. Eine weitere Methode wird benötigt — CTextBox::ShiftOnePositionUp(). Auch brauchen wir die Hilfsmethode CTextBox::LineCopy(), die das Kopieren von Zeilen erleichtert. 

class CTextBox : public CElement
  {
private:
   //--- Erstellt eine Kopie der angegebenen (Quelle) Zeile am neuen Ort (Ziel)
   void              LineCopy(const uint destination,const uint source);
  };
//+------------------------------------------------------------------+
//| Größenänderung de Arrays der spezifischen Zeile                  |
//+------------------------------------------------------------------+
void CTextBox::LineCopy(const uint destination,const uint source)
  {
   ::ArrayCopy(m_lines[destination].m_width,m_lines[source].m_width);
   ::ArrayCopy(m_lines[destination].m_symbol,m_lines[source].m_symbol);
  }

Der Code der Methode CTextBox::ShiftOnePositionUp() ist unten aufgeführt. In der ersten Schleife dieser Methode werden alle Zeilen unter der aktuellen Position des Kursors um eines nach oben geschoben. Bei der ersten Iteration muss überprüft werden, ob die Zeile Zeichen hat und, ist das der Fall, sichere sie, um sie der vorherigen Zeile hinten anzuhängen. Nachdem nun alle Zeilen verschoben sind, wird der Array der Zeilen um eins vermindert. Der Textkursor wird auf das Ende der vorherigen Zeile gestellt. 

Im letzten Block der Methode CTextBox::ShiftOnePositionUp() werden die Zeichen der gelöschten Zeile der vorherigen Zeile angehängt. Gibt es eine Zeile, die angehängt werden muss, verwenden wir die Funktion ::StringToCharArray(), um sie in ein Hilfsarray des Typs uchar, dem Typ der Zeichencodes, zu kopieren. Dann wird der Array der aktuellen Zeile um die Zahl der zu ergänzenden Zeichen erhöht. Am Ende werden dann die Zeichen und ihre Eigenschaften dem Array hinzugefügt. Die Umwandlung der Zeichencodes aus dem Hilfsarray mit dem Typ uchar wird von der Funktion ::CharToString() durchgeführt

class CTextBox : public CElement
  {
private:
   //--- Verschieben der Zeilen um eins nach oben
   void              ShiftOnePositionUp(void);
  };
//+------------------------------------------------------------------+
//| Verschieben der Zeilen um eins nach oben                         |
//+------------------------------------------------------------------+
void CTextBox::ShiftOnePositionUp(void)
  {
//--- Abfrage der Größe des Zeilenarrays
   uint lines_total=::ArraySize(m_lines);
//--- Verschieben der Zeilen um eins nach oben ab der nächsten Zeile
   for(uint i=m_text_cursor_y_pos; i<lines_total-1; i++)
     {
      //--- Die erste Iteration
      if(i==m_text_cursor_y_pos)
        {
         //--- Abfrage der Größe des Arrays mit den Zeichen
         uint symbols_total=::ArraySize(m_lines[i].m_symbol);
         //--- Gibt es Zeichen in dieser Zeile, sichere sie, um sie der Zeile drüber anzuhängen
         m_temp_input_string=(symbols_total>0)? CollectString(i) : "";
        }
      //--- Index des nächsten Elementes des Zeilenarrays
      uint next_index=i+1;
      //--- Abfrage der Größe des Arrays mit den Zeichen
      uint symbols_total=::ArraySize(m_lines[next_index].m_symbol);
      //--- Größenänderung des Arrays
      ArraysResize(i,symbols_total);
      //--- Erstelle eine Kopie der Zeile
      LineCopy(i,next_index);
     }
//--- Größenanpassung des Zeilenarrays
   uint new_size=lines_total-1;
   ::ArrayResize(m_lines,new_size);
//--- Vermindern des Zeilenzählers
   m_text_cursor_y_pos--;
//--- Abfrage der Größe des Arrays mit den Zeichen
   uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
//--- Stellen der Kursors ans Ende
   m_text_cursor_x_pos=symbols_total;
//--- Abfrage der Y-Koordinate des Textkursors
   CalculateTextCursorX();
//--- Gibt es eine Zeile, muss sie an die vorherige angehängt werden
   if(m_temp_input_string!="")
     {
      //--- Kopieren der Zeile in einen Array
      uchar array[];
      int total=::StringToCharArray(m_temp_input_string,array)-1;
      //--- Abfrage der Größe des Arrays mit den Zeichen
      symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
      //--- Größenänderung des Arrays
      new_size=symbols_total+total;
      ArraysResize(m_text_cursor_y_pos,new_size);
      //--- Ergänzen der Daten den Arrays der Struktur
      for(uint i=m_text_cursor_x_pos; i<new_size; i++)
        {
         m_lines[m_text_cursor_y_pos].m_symbol[i] =::CharToString(array[i-m_text_cursor_x_pos]);
         m_lines[m_text_cursor_y_pos].m_width[i]  =m_canvas.TextWidth(m_lines[m_text_cursor_y_pos].m_symbol[i]);
        }
     }
  }

Haben die Hilfsmethoden ihre Arbeit erledigt, erscheint der Coder der Hauptmethode CTextBox::OnPressedKeyBackspace() nicht mehr so kompliziert. Hier, ganz am Anfang, wird geprüft, ob die 'Backspace'-Taste gedrückt wurde, und ob das Textfeld aktiv ist. Wurden die Prüfungen bestanden, wird die Position des Textkursors abgefragt. Ist diese aktuell nicht am Anfang der Zeile, wird das vorherige Zeichen gelöscht. Ist sie jedoch der Anfang der Zeile und das ist nicht die erste Zeile, dann verschiebe alle nachfolgenden Zeilen um eins nach oben und lösche die aktuelle Zeile

Danach wird die neue Größe des Textfeldes berechnet und gesetzt. Die Grenzen und Koordinaten des Textkursors werden abgefragt. Der Schieberegler der Bildlaufleiste wird angepasst, wenn der Textkursor außerhalb der sichtbaren Teils ist. Zum Schluss wird das Textfeld neu gezeichnet mit einer unbedingten Darstellung des Textkursors und eins Nachricht über den veränderten Textkursor wird erstellt. 

class CTextBox : public CElement
  {
private:
   //--- Umgang mit der gedrückten 'Backspace'-Taste
   bool              OnPressedKeyBackspace(const long key_code);
  };
//+------------------------------------------------------------------+
//| Umgang mit der gedrückten 'Backspace'-Taste                      |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyBackspace(const long key_code)
  {
//--- Verlassen wenn die 'Backspace'-Taste nicht gedrückt oder das Textfeld nicht aktiv ist
   if(key_code!=KEY_BACKSPACE || !m_text_edit_state)
      return(false);
//--- Löschen des Zeichens, wenn Position größer Null
   if(m_text_cursor_x_pos>0)
      DeleteSymbol();
//--- Löschen der Zeile, wenn Position gleich Null und es nicht die erste Zeile ist
   else if(m_text_cursor_y_pos>0)
     {
      //--- Verschieben der Zeilen um eins
      ShiftOnePositionUp();
     }
//--- Berechne die Größe des Textfeldes
   CalculateTextBoxSize();
//--- Setze die neue Größe des Textfeldes
   ChangeTextBoxSize(true,true);
//--- Hole die Grenzen des sichtbaren Teils des Textfeldes
   CalculateBoundaries();
//--- Abfrage der X- und Y-Koordinaten des Kursors
   CalculateTextCursorX();
   CalculateTextCursorY();  
//--- Bewege die Bildlaufleiste, wenn der Textkursor den sichtbaren Teil verlässt
   if(m_text_cursor_x<=m_x_limit)
      HorizontalScrolling(CalculateScrollThumbX());
   else
     {
      if(m_text_cursor_x>=m_x2_limit)
         HorizontalScrolling(CalculateScrollThumbX2());
     }
//--- Bewege die Bildlaufleiste, wenn der Textkursor den sichtbaren Teil verlässt
   if(m_text_cursor_y<=m_y_limit)
      VerticalScrolling(CalculateScrollThumbY());
   else
      VerticalScrolling(m_scrollv.CurrentPos());
//--- Aktualisieren des Textes des Textfeldes
   DrawTextAndCursor(true);
//--- Senden einer Nachricht darüber
   ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
   return(true);
  }

 


Die Entertaste

Wenn im mehrzeiligen Modus die Entertaste gedrückt wird, muss eine neue Zeile eingefügt werden, und alle Zeilen unter der aktuellen Position des Textkursor müssen um eins nach unten geschoben werden. Dieses Verschieben der Zeilen benötigt eine eigene Hilfsmethode CTextBox:hiftOnePositionDown(), so wie auch eine weitere Methode um Zeilen zu löschen - CTextBox::ClearLine().

class CTextBox : public CElement
  {
private:
   //--- Löschen der angegebenen Zeile
   void              ClearLine(const uint line_index);
  };
//+------------------------------------------------------------------+
//| Löschen der angegebenen Zeile                                    |
//+------------------------------------------------------------------+
void CTextBox::ClearLine(const uint line_index)
  {
   ::ArrayFree(m_lines[line_index].m_width);
   ::ArrayFree(m_lines[line_index].m_symbol);
  }

Betrachten wir nun den Algorithmus der Methode CTextBox::ShiftOnePositionDown() im Detail. Zunächst muss die Zahl der Zeichen der Zeile gesichert werden, in der die Entertaste gedrückt wurde. Dies, wie auch die Position des Textkursor, bestimmen das Vorgehen in der Methode CTextBox::ShiftOnePositionDown(). Danach wird der Textkursor in die neue Zeile bewegt und die Größe des Zeilenarrays um eins erhöht. Dann werden alle Zeilen unter der aktuellen in einer Schleife beginnend mit der letzten um eins nach unten verschoben. In der letzten Iteration, wenn die Zeile, in der die Entertaste gedrückt wurde, keine Zeichen hat, muss die Zeile gelöscht werden, in der sich aktuell der Textkursor befindet. Die gelöschte Zeile ist eine Kopie der Zeile, deren Inhalt sich ja bereits in der nächsten Zeile befindet.

Zu Beginn hatten wir die Zeichenzahl der Zeile, in der die Entertaste gedrückt wurde, gesichert. Falls in dieser Zeile Zeichen sind, muss die Position des Textkursors im Augenblick des Tastendrucks festgestellt werden. Ist sie nicht das Ende der Zeile, muss die Anzahl der Zeichen, die in die neue Zeile verschoben werden müssen, ab der aktuellen Position des Textkursors bis zum Zeilenende berechnet werden. Dafür verwenden wir einen Hilfsarray, in den die Zeichen kopiert werden, die später in der neuen Zeile stehen werden.

class CTextBox : public CElement
  {
private:
   //--- Verschieben der Zeilen um eins nach unten
   void              ShiftOnePositionDown(void);
  };
//+------------------------------------------------------------------+
//| Verschieben der Zeilen um eins nach unten                        |
//+------------------------------------------------------------------+
void CTextBox::ShiftOnePositionDown(void)
  {
//--- Abfrage der Größe des Zeichenarrays der Zeile, in der die Entertaste gedrückt wurde
   uint pressed_line_symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
//--- Erhöhen des Zeilenzählers
   m_text_cursor_y_pos++;
//--- Abfrage der Größe des Zeilenarrays
   uint lines_total=::ArraySize(m_lines);
//--- Erhöhen der Arraygröße um eins
   uint new_size=lines_total+1;
   ::ArrayResize(m_lines,new_size);
//--- Verschieben der Zeilen ab der aktuellen Position um eins nach unten (beginnend vom Ende des Arrays)
   for(uint i=lines_total; i>m_text_cursor_y_pos; i--)
     {
      //--- Index des vorherigen Elementes des Zeilenarrays
      uint prev_index=i-1;
      //--- Abfrage der Größe des Arrays mit den Zeichen
      uint symbols_total=::ArraySize(m_lines[prev_index].m_symbol);
      //--- Größenänderung des Arrays
      ArraysResize(i,symbols_total);
      //--- Erstelle eine Kopie der Zeile
      LineCopy(i,prev_index);
      //--- Löschen der neuen Zeile
      if(prev_index==m_text_cursor_y_pos && pressed_line_symbols_total<1)
         ClearLine(prev_index);
     }
//--- Wenn die Entertaste nicht in einer Leerzeile gedrückt wurde
   if(pressed_line_symbols_total>0)
     {
      //--- Index der Zeile, in der die Entertaste gedrückt wurde
      uint prev_line_index=m_text_cursor_y_pos-1;
      //--- Array für die Kopie der Zeichen ab der aktuellen Position des Kursors bis zum Ende der Zeile
      string array[];
      //--- Setzen der Arraygröße auf die Zahl der Zeichen, die in die neue Zeile verschoben werden müssen
      uint new_line_size=pressed_line_symbols_total-m_text_cursor_x_pos;
      ::ArrayResize(array,new_line_size);
      //--- Kopiere die zu verschiebenden Zeichen in einen Array
      for(uint i=0; i<new_line_size; i++)
         array[i]=m_lines[prev_line_index].m_symbol[m_text_cursor_x_pos+i];
      //--- Größenänderung der Arrays der Struktur der Zeilen, in der die Entertaste gedrückt wurde
      ArraysResize(prev_line_index,pressed_line_symbols_total-new_line_size);
      //--- Größenänderung der Arrays der Struktur der neuen Zeile
      ArraysResize(m_text_cursor_y_pos,new_line_size);
      //--- Hinzufügen der Daten zu den des Arrays der Struktur der neuen Zeile
      for(uint k=0; k<new_line_size; k++)
        {
         m_lines[m_text_cursor_y_pos].m_symbol[k] =array[k];
         m_lines[m_text_cursor_y_pos].m_width[k]  =m_canvas.TextWidth(array[k]);
        }
     }
  }

Jetzt ist alles vorbereitet für die Bearbeitung eines Tastendrucks der Entertaste. Wenden wir uns der Methode CTextBox::OnPressedKeyEnter() zu. Ganz am Anfang wird überprüft, ob die Entertaste gedrückt wurde und das Textfeld aktiv ist. Wurden die Prüfungen bestanden und das Textfeld hat nur eine einzige Zeile, wird ihre Bearbeitung beendet. Es wird eine Ereignis mit den Identifikator ON_END_EDIT erzeugt und die Methode verlassen.

Ist der mehrzeilige Modus aktiv, werden alle unteren Zeilen ab der Position des Textkursors um eins nach unten verschoben. Danach wird die Größe des Textfeldes angepasst und es wird überprüft, ob sich der Textkursor unterhalb des sichtbaren Teils befindet. Zusätzlich wird der Textkursor an den Beginn der Zeile gestellt. Zuletzt wird das Textfeld neu gezeichnet und eine Nachricht gesendet, dass der Textkursor verschoben wurde. 

class CTextBox : public CElement
  {
private:
   //--- Umgang mit der gedrückten Entertaste
   bool              OnPressedKeyEnter(const long key_code);
  };
//+------------------------------------------------------------------+
//| Umgang mit der gedrückten Entertaste                             |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyEnter(const long key_code)
  {
//--- Verlassen, wenn die Entertaste nicht gedrückt wurde oder das Textfeld nicht aktiv ist
   if(key_code!=KEY_ENTER || !m_text_edit_state)
      return(false);
//--- Wenn der Mehrzeilenmodus deaktiviert ist
   if(!m_multi_line_mode)
     {
      //--- Deaktivieren des Textfeldes
      DeactivateTextBox();
      //--- Senden einer Nachricht darüber
      ::EventChartCustom(m_chart_id,ON_END_EDIT,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
      return(false);
     }
//--- Verschieben der Zeilen um eins nach unten
   ShiftOnePositionDown();
//--- Berechne die Größe des Textfeldes
   CalculateTextBoxSize();
//--- Setze die neue Größe des Textfeldes
   ChangeTextBoxSize();
//--- Hole die Grenzen des sichtbaren Teils des Textfeldes
   CalculateYBoundaries();
//--- Abfrage der Y-Koordinate des Textkursors
   CalculateTextCursorY();
//--- Bewege die Bildlaufleiste, wenn der Textkursor den sichtbaren Teil verlässt
   if(m_text_cursor_y+(int)LineHeight()>=m_y2_limit)
      VerticalScrolling(CalculateScrollThumbY2());
//--- Setze den Kursor an den Anfang der Zeile
   SetTextCursor(0,m_text_cursor_y_pos);
//--- Verschieben der Bildlaufleiste an den Anfang
   HorizontalScrolling(0);
//--- Aktualisieren des Textes des Textfeldes
   DrawTextAndCursor(true);
//--- Senden einer Nachricht darüber
   ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
   return(true);
  }

 


Die Tasten 'Left' und 'Right'

Durch das Drücken der Tasten 'Left' oder 'Right' wird der Kursor um ein Zeichen in die entsprechenden Richtung verschoben. Dafür benötigen wir zunächst eine weitere Methode CTextBox::CorrectingTextCursorXPos(), die die Position des Textkursor anpasst. Diese Methode wird auch von anderen Methoden dieser Klasse verwendet.

class CTextBox : public CElement
  {
private:
   //--- Anpassung des Textkursors bezüglich der X-Achse
   void              CorrectingTextCursorXPos(const int x_pos=WRONG_VALUE);
  };
//+------------------------------------------------------------------+
//| Anpassung des Textkursors bezüglich der X-Achse                  |
//+------------------------------------------------------------------+
void CTextBox::CorrectingTextCursorXPos(const int x_pos=WRONG_VALUE)
  {
//--- Abfrage der Größe des Arrays mit den Zeichen
   uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_width);
//--- Bestimmen der Kursorposition
   uint text_cursor_x_pos=0;
//--- Ist die Position verfügbar
   if(x_pos!=WRONG_VALUE)
      text_cursor_x_pos=(x_pos>(int)symbols_total-1)? symbols_total : x_pos;
//--- Ist die Position nicht verfügbar, stelle den Kursor ans Ende der Zeile
   else
      text_cursor_x_pos=symbols_total;
//--- Position Null, wenn die Zeile keine Zeichen enthält
   m_text_cursor_x_pos=(symbols_total<1)? 0 : text_cursor_x_pos;
//--- Abfrage der Y-Koordinate des Textkursors
   CalculateTextCursorX();
  }

Der Code unten ist der der Methode CTextBox::OnPressedKeyLeft() für die Taste 'Left'. Durch das Drücken einer anderen Taste oder, wenn das Textfeld nicht aktiv ist, wird die Methode verlassen, aber auch dann, wenn die 'Ctrl'-Taste zeitgleich gedrückt wurde. Das gleichzeitige Drücken von Tasten mit der der 'Ctrl'-Taste wird in einem späteren Teil des Artikels betrachtet. 

Nach bestandenen, ersten Prüfungen kommen wir zur Position des Textkursors. Steht der Kursor nicht am Anfang der Zeile, dann wird zum vorherigen Zeichen verschoben. Steht er am Anfang der Zeile und ist es nicht die erste Zeile, wird der Textkursor ans Ende der vorherigen Zeile gestellt. Danach muss der Schieberegler der horizontalen und vertikalen Bildlaufleiste angepasst, das Textfeld neu gezeichnet und eine Nachricht über den verschobenen Textkursor verschickt werden.

class CTextBox : public CElement
  {
private:
   //--- Gedrückte Taste 'Left'
   bool              OnPressedKeyLeft(const long key_code);
  };
//+------------------------------------------------------------------+
//| Gedrückte Taste 'Left'                                           |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyLeft(const long key_code)
  {
//--- Verlassen, wenn es nicht die 'Left'-Taste ist oder auch die 'Ctrl'-Taste gedrückt wurde oder das Textfeld nicht aktiv ist
   if(key_code!=KEY_LEFT || m_keys.KeyCtrlState() || !m_text_edit_state)
      return(false);
//--- Falls die Position des Textkursor größer Null ist
   if(m_text_cursor_x_pos>0)
     {
      //--- Bewegen zum vorherigen Zeichen
      m_text_cursor_x-=m_lines[m_text_cursor_y_pos].m_width[m_text_cursor_x_pos-1];
      //--- Verringern des Zeichenzählers
      m_text_cursor_x_pos--;
     }
   else
     {
      //--- Ist es nicht die erste Zeile
      if(m_text_cursor_y_pos>0)
        {
         //--- Ans Ende der vorherigen Zeile stellen
         m_text_cursor_y_pos--;
         CorrectingTextCursorXPos();
        }
     }
//--- Hole die Grenzen des sichtbaren Teils des Textfeldes
   CalculateBoundaries();
//--- Abfrage der Y-Koordinate des Textkursors
   CalculateTextCursorY();
//--- Bewege die Bildlaufleiste, wenn der Textkursor den sichtbaren Teil verlässt
   if(m_text_cursor_x<=m_x_limit)
      HorizontalScrolling(CalculateScrollThumbX());
   else
     {
      //--- Abfrage der Größe des Arrays mit den Zeichen
      uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
      //---
      if(m_text_cursor_x_pos==symbols_total && m_text_cursor_x>=m_x2_limit)
         HorizontalScrolling(CalculateScrollThumbX2());
     }
//--- Bewege die Bildlaufleiste, wenn der Textkursor den sichtbaren Teil verlässt
   if(m_text_cursor_y<=m_y_limit)
      VerticalScrolling(CalculateScrollThumbY());
//--- Aktualisieren des Textes des Textfeldes
   DrawTextAndCursor(true);
//--- Senden einer Nachricht darüber
   ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
   return(true);
  }

Kommen wir nun zur Methode CTextBox::OnPressedKeyRight() für den Tastendruck auf 'Right'. Zuerst wird der Tastendruck, die 'Ctrl'-Taste geprüft und, ob das Textfeld aktiv ist. Dann wird abgefragt, ob der Textkursor am Zeilenende steht. Falls nicht, wird der Textkursor um eine Zeichen nach rechts bewegt. Falls er doch am Zeilenende steht, dann prüfe, ob das die letzte Zeile ist. Falls nicht, stelle den Textkursor an den Anfang der nächsten Zeile.

Dann muss (1) der Schieberegler der Bildlaufleiste angepasst werden, wenn der Textkursor außerhalb des sichtbaren Teils ist, (2) das Textfeld neu gezeichnet werden und (3) eine Nachricht über den veränderten Textkursor gesendet werden.  

class CTextBox : public CElement
  {
private:
   //--- Gedrückte Taste 'Right'
   bool              OnPressedKeyRight(const long key_code);
  };
//+------------------------------------------------------------------+
//| Gedrückte Taste 'Right'                                          |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyRight(const long key_code)
  {
//--- Verlassen, wenn es nicht die 'Right'-Taste ist oder auch die 'Ctrl'-Taste gedrückt wurde oder das Textfeld nicht aktiv ist
   if(key_code!=KEY_RIGHT || m_keys.KeyCtrlState() || !m_text_edit_state)
      return(false);
//--- Abfrage der Größe des Arrays mit den Zeichen
   uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_width);
//--- Wenn am Zeilenende
   if(m_text_cursor_x_pos<symbols_total)
     {
      //--- Bewegen der Position des Textkursors zum nächsten Zeichen
      m_text_cursor_x+=m_lines[m_text_cursor_y_pos].m_width[m_text_cursor_x_pos];
      //--- Erhöhen des Zeichenzählers
      m_text_cursor_x_pos++;
     }
   else
     {
      //--- Abfrage der Größe des Zeilenarrays
      uint lines_total=::ArraySize(m_lines);
      //--- Ist es nicht die letzte Zeile
      if(m_text_cursor_y_pos<lines_total-1)
        {
         //--- Stellen des Kursors an den Anfang der nächsten Zeile
         m_text_cursor_x=m_text_x_offset;
         SetTextCursor(0,++m_text_cursor_y_pos);
        }
     }
//--- Hole die Grenzen des sichtbaren Teils des Textfeldes
   CalculateBoundaries();
//--- Abfrage der Y-Koordinate des Textkursors
   CalculateTextCursorY();
//--- Bewege die Bildlaufleiste, wenn der Textkursor den sichtbaren Teil verlässt
   if(m_text_cursor_x>=m_x2_limit)
      HorizontalScrolling(CalculateScrollThumbX2());
   else
     {
      if(m_text_cursor_x_pos==0)
         HorizontalScrolling(0);
     }
//--- Bewege die Bildlaufleiste, wenn der Textkursor den sichtbaren Teil verlässt
   if(m_text_cursor_y+(int)LineHeight()>=m_y2_limit)
      VerticalScrolling(CalculateScrollThumbY2());
//--- Aktualisieren des Textes des Textfeldes
   DrawTextAndCursor(true);
//--- Senden einer Nachricht darüber
   ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
   return(true);
  }

 


Die Tasten 'Up' und 'Down'

Das Drücken der Tasten 'Up' oder 'Down' bewirkt, dass der Textkursor sich von Zeile zu Zeile nach oben oder unten bewegt. Die Methoden CTextBox::OnPressedKeyUp() und CTextBox::OnPressedKeyDown() erledigen genau diese Aufgaben. Er wird nur der Code von einer der beiden hier aufgezeigt, da sie sich nur in zwei Zeilen unterscheiden.

Zu Beginn müssen drei Überprüfungen bestanden werden. Die Methode wird verlassen, wenn (1) es ein einzeiliges Textfeld ist, wenn (2) eine andere Taste gedrückt wurde oder, wenn (3) das Textfeld nicht aktiv ist. Ist der Textkursor augenblickliche nicht in der ersten Zeile, dann wird er in die vorherige Zeile gestellt (die nächste im Falle der Methode CTextBox::OnPressedKeyDown()) und es werden die Anzahl der Zeichen angepasst, wenn er jenseits des Zeilenendes platziert werden würde.

Danach wird, wenn der Textkursor außerhalb des sichtbaren Teils wäre, unter Umständen der Schieberegler der Bildlaufleiste angepasst. Der einzige Unterschied zwischen beiden Methoden ist jetzt, dass die Methode CTextBox::OnPressedKeyUp() das Überschreiten der Obergrenze , während die Methode CTextBox::OnPressedKeyDown() das Unterschreiten der Untergrenze prüft. Ganz am Ende wird das Textfeld aktualisiert und die Meldung über das Verschieben des Textkursor gesendet.

class CTextBox : public CElement
  {
private:
   //--- Tastendruck auf 'Up'
   bool              OnPressedKeyUp(const long key_code);
   //--- Tastendruck auf 'Down'
   bool              OnPressedKeyDown(const long key_code);
  };
//+------------------------------------------------------------------+
//| Tastendruck auf 'Up'                                             |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyUp(const long key_code)
  {
//--- Verlassen, wenn der mehrzeilige Modus nicht aktiv ist
   if(!m_multi_line_mode)
      return(false);
//--- Verlassen, wenn kein Tastendruck auf 'Up' oder das Textfeld nicht aktiv
   if(key_code!=KEY_UP || !m_text_edit_state)
      return(false);
//--- Abfrage der Größe des Zeilenarrays
   uint lines_total=::ArraySize(m_lines);
//--- Wenn der Bereich des Array nicht verletzt wurde
   if(m_text_cursor_y_pos-1<lines_total)
     {
      //--- Bewegen zur vorherigen Zeile
      m_text_cursor_y_pos--;
      //--- Anpassung des Textkursors bezüglich der X-Achse
      CorrectingTextCursorXPos(m_text_cursor_x_pos);
     }
//--- Hole die Grenzen des sichtbaren Teils des Textfeldes
   CalculateBoundaries();
//--- Abfrage der Y-Koordinate des Textkursors
   CalculateTextCursorY();
//--- Bewege die Bildlaufleiste, wenn der Textkursor den sichtbaren Teil verlässt
   if(m_text_cursor_x<=m_x_limit)
      HorizontalScrolling(CalculateScrollThumbX());
//--- Bewege die Bildlaufleiste, wenn der Textkursor den sichtbaren Teil verlässt
   if(m_text_cursor_y<=m_y_limit)
      VerticalScrolling(CalculateScrollThumbY());
//--- Aktualisieren des Textes des Textfeldes
   DrawTextAndCursor(true);
//--- Senden einer Nachricht darüber
   ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
   return(true);
  }



Die Tasten 'Home' und 'End'

Ein Tastendruck auf 'Home' oder 'End' bewegt den Textkursor an den Beginn oder an das Ende der Zeile. Die Methoden CTextBox::OnPressedKeyHome() und CTextBox::OnPressedKeyEnd() erledigen genau diese Aufgaben. Ihr Code ist unten aufgeführt und er bedarf keiner weiteren Erläuterungen, da er ziemlich einfach und ausführlich kommentiert ist. 

class CTextBox : public CElement
  {
private:
   //--- Tastendruck auf 'Home'
   bool              OnPressedKeyHome(const long key_code);
   //--- Tastendruck auf 'End'
   bool              OnPressedKeyEnd(const long key_code);
  };
//+------------------------------------------------------------------+
//| Tastendruck auf 'Home'                                           |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyHome(const long key_code)
  {
//--- Verlassen, wenn 'End' nicht oder zugleich mit 'Ctrl' gedrückt wurde oder das Textfeld nicht aktiv ist
   if(key_code!=KEY_HOME || m_keys.KeyCtrlState() || !m_text_edit_state)
      return(false);
//--- Bewegen des Kursors an den Zeilenanfang
   SetTextCursor(0,m_text_cursor_y_pos);
//--- Bewegen der Bildlaufleiste auf die erste Stelle
   HorizontalScrolling(0);
//--- Aktualisieren des Textes des Textfeldes
   DrawTextAndCursor(true);
//--- Senden einer Nachricht darüber
   ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
   return(true);
  }
//+------------------------------------------------------------------+
//| Drücken der Taste 'End'                                          |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyEnd(const long key_code)
  {
//--- Verlassen, wenn 'End' nicht oder zugleich mit 'Ctrl' gedrückt wurde oder das Textfeld nicht aktiv ist
   if(key_code!=KEY_END || m_keys.KeyCtrlState() || !m_text_edit_state)
      return(false);
//--- Abfrage der Zeichenzahl der aktuellen Zeile
   uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
//--- Bewegen des Kursors an das Zeilenende
   SetTextCursor(symbols_total,m_text_cursor_y_pos);
//--- Abfrage der Y-Koordinate des Textkursors
   CalculateTextCursorX();
//--- Hole die Grenzen des sichtbaren Teils des Textfeldes
   CalculateXBoundaries();
//--- Bewege die Bildlaufleiste, wenn der Textkursor den sichtbaren Teil verlässt
   if(m_text_cursor_x>=m_x2_limit)
      HorizontalScrolling(CalculateScrollThumbX2());
//--- Aktualisieren des Textes des Textfeldes
   DrawTextAndCursor(true);
//--- Senden einer Nachricht darüber
   ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
   return(true);
  }

 


Gleichzeitiges Drücken einer Taste mit der Ctrl-Taste

Betrachten wir nun die Methoden für die Handhabung der folgenden Tastenkombinationen:

  • 'Ctrl' + 'Left' – Bewegen des Textkursor von Wort zu Wort nach links.
  • 'Ctrl' + 'Right' – Bewegen des Textkursor von Wort zu Wort nach rechts.
  • 'Ctrl' + 'Home' – Bewegen des Textkursor an den Anfang der ersten Zeile. 
  • 'Ctrl' + 'End' – Bewegen des Textkursor an das Ende der letzten Zeile.

Als Beispiel wird aber nur eine der Methoden erläutert — CTextBox::OnPressedKeyCtrlAndLeft(), mit der der Textkursor von Wort zu Wort nach links springt. Zu Beginn wird überprüft, ob 'Ctrl' und 'Left' zugleich gedrückt wurden. Wurde einer der beiden Tasten nicht gedrückt, wird die Methode verlassen. Weiters muss das Textfeld aktiv sein.

Falls der Textkursor aktuell am Anfang einer Zeile steht, die nicht die erste ist, stelle ihn ans Ende der vorherigen Zeile. Steht der Textkursor nicht am Zeilenanfang, dann muss der Anfang einer Unterbrechung einer Reihe von Zeichen gefunden werden. Das Leerzeichen (' ') dient als Unterbrechung. Jetzt bewegen wir uns in einer Schleife von rechts nach links, und sobald eine Kombination aus einem aktuellen Nicht-Leerzeichen und folgendem Leerzeichen gefunden wurde, ist das die Startposition und der Textkursor wird hier hin gestellt.

Danach, wie in allen anderen Methoden, wird überprüft, ob der Textkursor außerhalb des sichtbaren Teils des Textfeldes ist, um dann den Schieberegler und die Bildlaufleiste anzupassen. Zuletzt wird das Textfeld neu gezeichnet und eine Nachricht gesendet, dass der Textkursor verschoben wurde.

class CTextBox : public CElement
  {
private:
   //--- Zeitgleiches Drücken der Tasten 'Ctrl' + 'Left'
   bool              OnPressedKeyCtrlAndLeft(const long key_code);
   //--- Zeitgleiches Drücken der Tasten 'Ctrl' + 'Right'
   bool              OnPressedKeyCtrlAndRight(const long key_code);
   //--- Zeitgleiches Drücken der Tasten 'Ctrl' + 'Home'
   bool              OnPressedKeyCtrlAndHome(const long key_code);
   //--- Zeitgleiches Drücken der Tasten 'Ctrl' + 'End'
   bool              OnPressedKeyCtrlAndEnd(const long key_code);
  };
//+------------------------------------------------------------------+
//| Zeitgleiches Drücken der Tasten 'Ctrl' + 'Left'                  |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyCtrlAndLeft(const long key_code)
  {
//--- Verlassen, wenn (1) 'Left' oder (2) 'Ctrl' nicht gedrückt wurden oder (3) das Textfeld nicht aktiv ist
   if(!(key_code==KEY_LEFT && m_keys.KeyCtrlState()) || !m_text_edit_state)
      return(false);
//--- Leerzeichen
   string SPACE=" ";
//--- Abfrage der Größe des Zeilenarrays
   uint lines_total=::ArraySize(m_lines);
//--- Abfrage der Zeichenzahl der aktuellen Zeile
   uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
//--- Steht der Kursor am Anfang der aktuellen Zeile, die nicht die erste ist,
//    stelle den Kursor an das Ende der vorherigen Zeile
   if(m_text_cursor_x_pos==0 && m_text_cursor_y_pos>0)
     {
      //--- Abfrage des Index der vorherigen Zeile
      uint prev_line_index=m_text_cursor_y_pos-1;
      //--- Abfrage der Zeichenzahl der vorherigen Zeile
      symbols_total=::ArraySize(m_lines[prev_line_index].m_symbol);
      //--- Stelle den Kursor an das Ende der vorherigen Zeile
      SetTextCursor(symbols_total,prev_line_index);
     }
// --- Steht der Kursor am Anfang der ersten Zeile
   else
     {
      //--- Finde den Anfang einer fortlaufenden Reihe von Zeichen (von links nach rechts)
      for(uint i=m_text_cursor_x_pos; i<=symbols_total; i--)
        {
         //--- Gehe zur nächsten, wenn der Kursor ans Ende der Zeile gelangt ist
         if(i==symbols_total)
            continue;
         //--- Wenn es das erste Zeichen der Zeile ist
         if(i==0)
           {
            //--- Stelle den Kursor an den Zeilenanfang
            SetTextCursor(0,m_text_cursor_y_pos);
            break;
           }
         //--- Wenn es nicht das erste Zeichen der Zeile ist
         else
           {
            //--- Wurde erstmals der Anfang einer fortlaufenden Reihe gefunden.
            //    Wird der Index des dem Leerzeichen folgenden Zeichens als Wortanfang betrachtet.
            if(i!=m_text_cursor_x_pos &&  
               m_lines[m_text_cursor_y_pos].m_symbol[i]!=SPACE &&  
               m_lines[m_text_cursor_y_pos].m_symbol[i-1]==SPACE)
              {
               //--- Stelle den Kursor auf den Anfang der nächsten fortlaufenden Reihe
               SetTextCursor(i,m_text_cursor_y_pos);
               break;
              }
           }
        }
     }
//--- Hole die Grenzen des sichtbaren Teils des Textfeldes
   CalculateBoundaries();
//--- Abfrage der Y-Koordinate des Textkursors
   CalculateTextCursorX();
//--- Abfrage der Y-Koordinate des Textkursors
   CalculateTextCursorY();
//--- Bewege die Bildlaufleiste, wenn der Textkursor den sichtbaren Teil verlässt
   if(m_text_cursor_x<=m_x_limit)
      HorizontalScrolling(CalculateScrollThumbX());
   else
     {
      //--- Abfrage der Größe des Arrays mit den Zeichen
      symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
      //---
      if(m_text_cursor_x_pos==symbols_total && m_text_cursor_x>=m_x2_limit)
         HorizontalScrolling(CalculateScrollThumbX2());
     }
//--- Bewege die Bildlaufleiste, wenn der Textkursor den sichtbaren Teil verlässt
   if(m_text_cursor_y<=m_y_limit)
      VerticalScrolling(CalculateScrollThumbY());
//--- Aktualisieren des Textes des Textfeldes
   DrawTextAndCursor(true);
//--- Senden einer Nachricht darüber
   ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
   return(true);
  }

Das Studium der anderen Methoden dieses Kapitels bleibt dem Leser überlassen. 

 


Integration des Textfeldes in die Bibliothek

Damit ein mehrzeiliges Textfeld korrekt arbeitet, wird ein privater Array in der Struktur WindowElements der Klasse CWndContainer benötigt. Laden Sie die Datei mit der Klasse CTextBox in die Datei WndContainer.mqh:

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#include "Controls\TextBox.mqh"

Hinzufügen der Struktur WindowElements dem privaten Array für das neue Textfeld

//+------------------------------------------------------------------+
//| Klasse zum Sichern aller Interface-Objekte                       |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- Struktur des Arrays der Elemente
   struct WindowElements
     {
      //--- Mehrzeilige Textfelder
      CTextBox         *m_text_boxes[];
     };
   //--- Ein Array der Elemente für jedes Fenster
   WindowElements    m_wnd[];
  };

Da die Textfelder des Typs CTextBox zusammengesetzt sind und andere Typen verwendet werden (hier die Bildlaufleiste), wird eine Methode benötigt, die die Pointer zu diesen Textfeldern für die entsprechenden, privaten Arrays bereithält. Der Code unten zeigt die Methode CWndContainer::AddTextBoxElements(), die das bewältigt. Diese Methode wird am selben Ort wie die anderen aufgerufen, das ist CWndContainer::AddToElementsArray(). 

class CWndContainer
  {
private:
   //--- Sichern des Objektpointers des Textfeldes
   bool              AddTextBoxElements(const int window_index,CElementBase &object);
  };
//+------------------------------------------------------------------+
//| Sichern des Objektpointers des Textfeldes                        |
//+------------------------------------------------------------------+
bool CWndContainer::AddTextBoxElements(const int window_index,CElementBase &object)
  {
//--- Verlassen, wenn es kein mehrzeiliges Textfeld ist
   if(dynamic_cast<CTextBox *>(&object)==NULL)
      return(false);
//--- Abfrage des Objektpointers auf das Textfeld
   CTextBox *tb=::GetPointer(object);
   for(int i=0; i<2; i++)
     {
      int size=::ArraySize(m_wnd[window_index].m_elements);
      ::ArrayResize(m_wnd[window_index].m_elements,size+1);
      if(i==0)
        {
         //--- Abfrage des Pointers auf die Bildlaufleiste
         CScrollV *sv=tb.GetScrollVPointer();
         m_wnd[window_index].m_elements[size]=sv;
         AddToObjectsArray(window_index,sv);
         //--- Hinzufügen des Pointers zum privaten Array
         AddToRefArray(sv,m_wnd[window_index].m_scrolls);
        }
      else if(i==1)
        {
         CScrollH *sh=tb.GetScrollHPointer();
         m_wnd[window_index].m_elements[size]=sh;
         AddToObjectsArray(window_index,sh);
         //--- Hinzufügen des Pointers zum privaten Array
         AddToRefArray(sh,m_wnd[window_index].m_scrolls);
        }
     }
//--- Hinzufügen des Pointers zum privaten Array
   AddToRefArray(tb,m_wnd[window_index].m_text_boxes);
   return(true);
  }

Jetzt muss noch die Methode CWndEvents::OnTimerEvent() ergänzt werden. Erinnern wir uns, die grafische Benutzerinterface wird nur dann neu gezeichnet, wenn sich der Kursor bewegt und wird zeitweise ausgesetzt, nachdem die Mausbewegung sich beendete. Es sollte eine Ausnahme für die Elemente des Typs CTextBox gemacht werden. Andernfalls, wenn das Eingabefeld aktiviert ist, würde der Textkursor nicht blinken. 

//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CWndEvents::OnTimerEvent(void)
  {
//--- Verlassen, wenn der Mauskursor in Ruhe ist, (Differenz zwischen den Aufrufen >300 ms) und die linke Maustaste losgelassen wurde
   if(m_mouse.GapBetweenCalls()>300 && !m_mouse.LeftButtonState())
     {
      int text_boxes_total=CWndContainer::TextBoxesTotal(m_active_window_index);
      for(int e=0; e<text_boxes_total; e++)
         m_wnd[m_active_window_index].m_text_boxes[e].OnEventTimer();
      //---
      return;
     }
//--- Verlassen, wenn das Array leer ist  
   if(CWndContainer::WindowsTotal()<1)
      return;
//--- Ereignisse aller Elemente durch den Timer überprüfen
   CheckElementsEventsTimer();
//--- Chart Neuzeichnen
   m_chart.Redraw();
  }

Jetzt können wir eine MQL-Anwendung erstellen, mit der das mehrzeilige Textfeld getestet werden kann. 

 


Anwendung zum Testen des Textfeldes

Für den Test erstellen wir eine MQL-Anwendung mit einer grafischen Benutzerinterface mit zwei Textfeldern. Eine im einzeiligen, die andere im mehrzeiligen Modus. Zusätzlich zu diesen Textfeldern verfügt die grafische Benutzerinterface über ein Hauptmenü mit Statuszeile. Im zweiten Teil dieses Textfeldes wird die Position des Textkursors des mehrzeilige Textfeldes angezeigt.

Wir erstellen zwei Instanzen der Klasse CTextBox und deklarieren zwei Methoden zum Erstellen der Textfelder:

class CProgram : public CWndEvents
  {
protected:
   //--- Bearbeitet
   CTextBox          m_text_box1;
   CTextBox          m_text_box2;
   //---
protected:
   //--- Bearbeitet
   bool              CreateTextBox1(const int x_gap,const int y_gap);
   bool              CreateTextBox2(const int x_gap,const int y_gap);
  };

Der Code unten ist der der zweiten Methode, um ein mehrzeiliges Textfeld zu erzeugen. Um den mehrzeiligen Modus zu aktivieren, verwenden wir die Methode CTextBox::MultiLineMode(). Mit der Methode CElementBase::AutoXResizeXXX() wird die Größe automatisch angepasst. Als Beispiel fügen wir den Inhalt dieses Artikels in das mehrzeilige Textfeld. Dafür bereiten wir einen Array mit Zeilen vor, der später in einer Schleife unter Verwendung der Methoden der Klasse CTextBox eingefügt werden kann. 

//+------------------------------------------------------------------+
//| Erstellung eines mehrzeiligen Textfeldes                  |
//+------------------------------------------------------------------+
bool CProgram::CreateTextBox2(const int x_gap,const int y_gap)
  {
//--- Sichere den Pointer des Fenster
   m_text_box2.WindowPointer(m_window);
//--- Setze Eigenschaften vor der Erstellung
   m_text_box2.FontSize(8);
   m_text_box2.Font("Calibri"); // Consolas|Calibri|Tahoma
   m_text_box2.AreaColor(clrWhite);
   m_text_box2.TextColor(clrBlack);
   m_text_box2.MultiLineMode(true);
   m_text_box2.AutoXResizeMode(true);
   m_text_box2.AutoXResizeRightOffset(2);
   m_text_box2.AutoYResizeMode(true);
   m_text_box2.AutoYResizeBottomOffset(24);
//--- Array der Zeilen
   string lines_array[]=
     {
      "Einführung",
      "Tastengruppen und Tastaturlayout",
      "Umgang mit Tastendruck-Ereignissen",
      "ASCII-Codes der Zeichen und Kontrolltasten",
      "Scancodes der Tasten",
      "Hilfsklassen für die Tastatur",
      "Das mehrzeilige Textfeld",
      "Entwicklung der Klasse des Eingabefeldes CTextBox",
      "Eigenschaften und Aussehen",
      "Handhabung des Textkursors",
      "Zeicheneingabe",
      "Die Taste 'Backspace'",
      "Die Taste 'Enter'",
      "Die Tasten 'Left' und 'Right'",
      "Die Tasten 'Up' und 'Down'",
      "Die Taste 'Home' und 'End'",
      "Zeitgleicher Tastendruck einer Taste mit 'Ctrl'",
      "Integration des Textfeldes in die Bibliothek",
      "Anwendungsbeispiel des Textfeldes",
      "Abschluss"
     };
//--- Einfügen des Textes in das Textfeld
   int lines_total=::ArraySize(lines_array);
   for(int i=0; i<lines_total; i++)
     {
      //--- Einfügen in die erste Zeile
      if(i==0)
         m_text_box2.AddText(0,lines_array[i]);
      //--- Einfügen einer Zeile in das Textfeld
      else
         m_text_box2.AddLine(lines_array[i]);
     }
//--- Erstellen der Textfelder
   if(!m_text_box2.CreateTextBox(m_chart_id,m_subwin,x_gap,y_gap))
      return(false);
//--- Einfügen eines Objektes in den allgemeinen Array der Objekte
   CWndContainer::AddToElementsArray(0,m_text_box2);
//--- Einfügen des Text in die Statuszeile
   m_status_bar.ValueToItem(1,m_text_box2.TextCursorInfo());
   return(true);
  }

Fügen Sie den folgenden Code dem Event Handler der MQL-Anwendung hinzu, um Nachrichten des Textfeldes erhalten zu können:

//+------------------------------------------------------------------+
//| Chart Event Handler                                              |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Ereignis (1) einer Werteingabe oder (2) der Aktivierung des Textfeldes oder (3) einer Bewegung des Textkursors
   if(id==CHARTEVENT_CUSTOM+ON_END_EDIT ||
      id==CHARTEVENT_CUSTOM+ON_CLICK_TEXT_BOX ||
      id==CHARTEVENT_CUSTOM+ON_MOVE_TEXT_CURSOR)
     {
      ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);

      //--- Wenn der Identifikator passt (Nachricht des mehrzeiligen Textfeldes)
      if(lparam==m_text_box2.Id())
        {
         //--- Aktualisieren des zweiten Elementes der Statuszeile
         m_status_bar.ValueToItem(1,sparam);
        }
      //--- Neuzeichnen des Charts
      m_chart.Redraw();
      return;
     }
  }


Nach der Kompilierrung der Anwendung und ihrem Start auf dem Chart erscheint folgendes:

 Fig. 9. Grafische Benutzeroberfläche mit dem Beispiel eines Textfeldes

Fig. 9. Grafische Benutzerinterface mit dem Beispiel eines Textfeldes

 

Die Testanwendung dieses Artikels kann mittel des Links unten heruntergeladen werden, für ein weiteres Studium. 

 


Schlussfolgerung

Zur Zeit schaut das Schema der Bibliothek zum Erstellen einer grafische Benutzerinterface wie folgt aus:

 Fig. 10. Struktur der Bibliothek im augenblicklichen Entwicklungszustand.

Fig. 10. Struktur der Bibliothek im augenblicklichen Entwicklungszustand.

 

In der nächsten Version der Bibliothek, wird manches weiterentwickelt und neue Funktionen den bereits bestehenden hinzugefügt. Unten könne Sie die letzten Versionen der Bibliothek und der Dateien zum Testen herunterladen.

Bei Fragen zur Verwendung des bereitgestellten Materials, können Sie auf die detaillierten Beschreibungen im Lauf der Entwicklung der Bibliothek in den vorangegangenen Artikeln dieser Serie zurückgreifen oder Sie stellen Ihre Fragen im Kommentarteil diese Artikels. 


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

Beigefügte Dateien |
Letzte Kommentare | Zur Diskussion im Händlerforum (3)
Christian
Christian | 10 Sept. 2017 in 11:50

Wie aktiviert man eine Textbox so das man direkt neu eingeben kann  ohne die Mouse zu benutzen ? 


Es ist ein einzeiliges Element. Was als Kommando Eingabefeld agieren soll.

Das nach jedem Enter das Kommando weiterleitet.  

Nach dem Drücken der Enter Taste verliert die Textbox den Eingabestatus


Finde leider keine Funktion dazu in der TextBox.mqh oder ElementBase.mqh

Carl Schreiber
Carl Schreiber | 10 Sept. 2017 in 12:47

Schau mal auf der Seite des Autors:

Dort gibt es eine neue Version des Textfeldes und zwei generelle Updates des Gesamtcodes und zwei noch nicht übersetzte Artikel, vielleicht ist da etwas dabei, sonst schreib ihm eine private mail am besten in Deutsch und den Übersetzungen in engl. und russisch.

Christian
Christian | 10 Sept. 2017 in 14:01
Carl Schreiber:

Schau mal auf der Seite des Autors:

Dort gibt es eine neue Version des Textfeldes und zwei generelle Updates des Gesamtcodes und zwei noch nicht übersetzte Artikel, vielleicht ist da etwas dabei, sonst schreib ihm eine private mail am besten in Deutsch und den Übersetzungen in engl. und russisch.


Sehr gut Carl  !

Super Tipp .... ist tatsächlich in der nächsten Version enthalten . Hab das mal übersetzt. 

Ich muss wohl mal öfters bei den Autoren stöbern.

Die gesuchte Funktion ist CTextBox::ActivateTextBox()

Gruß Christian

Nach dem Doppelklick auf die Tabellenzelle erscheint das Eingabefeld,
 aber um einen weiteren Klick auszuschließen,
 um das Eingabefeld zu aktivieren,
 wurde eine zusätzliche öffentliche Methode CTextBox :: ActivateTextBox () benötigt. 
Sein Anruf simuliert einen Klick auf das Eingabefeld.
 Um dies zu tun, rufen Sie einfach die Methode CTextBox :: OnClickTextBox () auf und übergeben Sie den Namen des Grafikobjekts des Elements. 
Die Auswahl des Textes erfolgt bereits bei dieser Methode
Universeller Kanal mit grafischem Interface Universeller Kanal mit grafischem Interface
Alle Channel Indikatoren stellen drei Linien dar: zentrale, obere und untere Linie. Die zentrale Linie ist nach dem Prinzip des Zeichnens dem gleitenden Durchschnitt identisch, und in den meisten Fällen wird für das Zeichnen eines Kanals gerade der gleitende Durchschnitt verwendet. Die obere und untere Linien sind von der zentralen Linie gleich weit entfernt. Dieser Abstand kann in Punkten oder in Prozent vom Preis (Envelopes Indikator) definiert werden, es können der Wert der Standardabweichung (Bolliger Bands) oder der Wert des ATR Indikators (Keltner Kanal) verwendet werden.
ZUP - Universal-ZigZag mit Pesavento-Mustern. Grafische Benutzeroberfläche ZUP - Universal-ZigZag mit Pesavento-Mustern. Grafische Benutzeroberfläche
In den zehn Jahren seit der Veröffentlichung der ersten Version der ZUP-Plattform gab es mehrere Änderungen und Verbesserungen. Im Ergebnis haben wir jetzt eine einzigartige, grafische Erweiterung für den MetaTrader 4, die Ihnen eine schnelle und bequeme Analyse des Marktes erlaubt. Der Artikel beschreibt, wie Sie mit der grafischen Oberfläche des ZUP-Indikators arbeiten können.
Grafische Interfaces X: Neue Möglichkeiten der Tabellendarstellung (build 9) Grafische Interfaces X: Neue Möglichkeiten der Tabellendarstellung (build 9)
Bis heute war CTable die fortschrittlichste Tabellenart überhaupt in der Bibliothek. Diese Tabelle ist zusammengestellt aus editierbaren Boxen des Typs OBJ_EDIT Typ, aber eine weitere Entwicklung ist problematisch. Mit dem Ziel einer maximale Leistungsfähigkeit, wäre es besser, eine andere Tabellendarstellung vom Typ CCanvasTable zu entwickeln, auch beim augenblicklichen Entwicklungsstand der Bibliothek. Die aktuelle Version ist völlig starr, aber ab diesem Artikel werden wir versuchen, die Situation zu beheben.
Die Sprache MQL4 für "Neulinge". Die benutzerdefinierten Indikatoren (Teil 2) Die Sprache MQL4 für "Neulinge". Die benutzerdefinierten Indikatoren (Teil 2)
Es ist der fünfte Artikel aus der Reihe "Die Sprache MQL4 für "Neulinge". Heute lernen wir, die graphischen Objekte zu verwenden - das ist ein sehr mächtiges Mittel der Entwicklung, welches ermöglicht, die Möglichkeiten der Indikatoren wesentlich zu verbreiten. Außerdem können Sie sie auch in Skripten und in den EAs verwenden. Wir erkennen, wie Objekte erstellt werden können, ihre Parameter ändern, Fehler überprüfen. Natürlich gelingt es mir nicht, vollständig alle Objekte zu beschreiben, sie sind zu viel. Aber Sie bekommen alle notwendige Information,dies alles selbst zu begreifen. Auch enthält dieser Artikel ein Anleitung-Beispiel, einen komplizierten Signalindikator Schritt um Schritt zu erstellen. Dabei werden viele Parameter dem Benutzer für die Einstellung zugänglich sein, was flexibel ermöglicht, die Aussicht zu ändern.