Grafische Interfaces X: mehrzeiliges Textfeld (build 8)
Inhalt
- Einführung
- Tastengruppen und Tastaturlayout
- Umgang mit Tastendruck-Ereignissen
- ASCII-Codes der Buchstaben und die Kontrolltasten
- Scancodes der Tasten
- Hilfsklassen für die Tastatur
- Das mehrzeilige Textfeld.
- Entwicklung der Klasse des Eingabefeldes
- Integration des Textfeldes in die Bibliothek
- Anwendung zum Testen des Textfeldes
- Schlussfolgerung
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).
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.
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 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.
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. 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 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.
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
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.
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
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.
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