Graphisches Interface X: Textauswahl im mehrzeiligen Textfeld (build 13)
Inhaltsverzeichnis
- Einführung
- Abfangen der gedrückten Shift-Taste
- Tastenkombinationen zum Markieren von Text
- Verfahren zum Auswählen von Text
- Methoden zum Löschen des ausgewählten Textes
- Klasse für das Arbeiten mit Bilddaten
- Schlussfolgerung
Einführung
Der erste Artikel Grafische Interfaces I: Vorbereitung der Bibliotheksstruktur (Kapitel 1) beschreibt im Detail den Zweck der Bibliothek. Eine vollständige Liste mit den Verweisen auf die Artikel des ersten Teils findet sich am Ende jeden Kapitels. Dort können Sie auch die komplette, aktuelle Version der Bibliothek zum derzeitigen Entwicklungsstand herunterladen. Die Dateien müssen in die gleichen Verzeichnissen kopiert werden, wie sie sich in dem Archiv befinden.
Um das mehrzeilige Textfeld aus den unten angegebenen Artikeln richtig nutzen zu können, sollte es möglich sein, Text zu markieren, denn ein Löschen von Text, Buchstabe für Buchstabe, ist ziemlich unbequem.
- Grafisches Interface X: mehrzeiliges Textfeld (build 8)
- Graphisches Interface X: Algorithmus zum Zeilenumbruch in mehrzeiligen Textfeldern (build 12)
Die Textauswahl mittels verschiedener Tastenkombinationen und das Löschen von markiertem Text funktioniert genau so wie in jedem anderen Texteditor. Zusätzlich wird der Code weiter optimiert, und es werden die Klassen für die zweite Stufe in Richtung der endgültigen Version der Bibliothek vorbereitet, die alle Elemente als Einzelbilder vor einem Hintergrund darstellt.
Die endgültige Version dieses Elementes wird hier entwickelt. Jede spätere Änderung wird nur dann vorgenommen werden, wenn für einen bestimmten Algorithmus eine effizientere Lösung gefunden werden würde.
Abfangen der gedrückten Shift-Taste
Ergänzen wir als aller erstes die bereits besprochenen Klasse CKeys mit der Methode CKeys::KeyShiftState(), um den aktuellen Zustand der Shift-Taste abzufragen. Diese Taste wird in mehreren Kombinationen zur Textauswahl verwendet. Unten ist der Code dieser einfachen Methode aufgelistet. Die Shift-Taste gilt als gedrückt, wenn die Funktion ::TerminalInfoInteger() mit der Identifikator TERMINAL_KEYSTATE_SHIFT einen Wert ungleich Null zurückgibt.
//+------------------------------------------------------------------+ //| Arbeitsklasse der Tastatur | //+------------------------------------------------------------------+ class CKeys { public: //--- Rückgabe des Zustandes der Shift-Taste bool KeyShiftState(void); }; //+------------------------------------------------------------------+ //| Rückgabe des Zustandes der Shift-Taste | //+------------------------------------------------------------------+ bool CKeys::KeyShiftState(void) { return(::TerminalInfoInteger(TERMINAL_KEYSTATE_SHIFT)<0); }
Tastenkombinationen zum Markieren von Text
Kommen wir jetzt zu allen Tastenkombinationen für die Textauswahl, die wir im Textfeld verwenden wollen. Beginnen wir mit der Kombination von zwei Tasten.
- Die Kombinationen von 'Shift + Links' und 'Shift + Rechts' bewegen den Kursor nach links bzw. rechts, jeweils um einen Buchstaben. Der Hintergrund und die Buchstaben des Textes werden durch andere Farben (anpassbar durch Benutzer) hervorgehoben:
Fig. 1 Textauswahl durch eine buchstabenweise Kursorbewegung nach links und rechts.
- Die Kombinationen 'Shift + Pos1' and 'Shift + Ende' bewegen den Textkursor an den Anfang oder das Ende einer Zeile und markieren alle Buchstaben ab der Kursorposition.
Fig. 2. Textauswahl mit dem Kursor ab der Startposition bis zum Anfang und zum Ende der Zeile.
- Die Kombinationen von 'Shift + Hoch' und 'Shift + Runter' bewegen den Kursor eine Zeile nach oben oder unten. Der Text wird ab der Startposition des Kursors markiert, nach oben über den Zeilenanfang und nach unten über das Zeilenende bis zur Endposition des Kursor. Gibt es mehr als eine Zeile zwischen Anfangs- und Endzeile, werden diese zur Gänze markiert.
Fig. 3. Textauswahl durch Auf- und Abbewegungen.
Manchmal werden Kombination aus drei Tasten zur Textauswahl verwendet. Wenn zum Beispiel schnell mehrere Worte einer Zeile ausgewählt werden sollen, ein Markieren, Buchstabe für Buchstabe, wäre zu langwierig. Auch wenn es notwendig sein sollte, Text auszuwählen, der mehrere Zeilen umfasst, wäre eine Auswahl Zeile für Zeile nicht so bequem.
Die Kombination aus drei Tasten verwendet die Taste Ctrl zusammen mit Shift. Betrachten wir alle Kombinationen, die in diesem Artikel beschrieben werden:
- Die Kombinationen 'Ctrl + Shift + Left' und 'Ctrl + Shift + Right' wählen ganze Wörter durch die Kursorbewegungen nach links oder rechts:
Fig. 4. Fig. 1 Textauswahl durch eine wortweise Kursorbewegung nach links und rechts.
- Die Kombinationen 'Ctrl + Shift + Home' und 'Ctrl + Shift + End' markieren den gesamten Text von der aktuellen Kursorposition bis zum Anfang der ersten oder dem Ende der letzten Zeile:
Fig. 5. Textauswahl mit dem Kursor ab der Startposition bis zum Anfang und zum Ende des Dokumentes.
Der nächste Abschnitt behandelt die Methoden der Textauswahl.
Verfahren zum Auswählen von Text
Standardmäßig wird der markierte Text mit weißen Buchstaben vor einem blauen Hintergrund dargestellt. Falls nötig, können mit den Methoden CTextBox:: SelectedBackColor() und CTextBox:: SelectedTextColor() die Farben geändert werden.
class CTextBox : public CElement { private: //--- Hintergrund- und Buchstabenfarben des markierten Textes color m_selected_back_color; color m_selected_text_color; //--- private: //--- Hintergrund- und Buchstabenfarben des markierten Textes void SelectedBackColor(const color clr) { m_selected_back_color=clr; } void SelectedTextColor(const color clr) { m_selected_text_color=clr; } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTextBox::CTextBox(void) : m_selected_text_color(clrWhite), m_selected_back_color(C'51,153,255') { //... }
Um Text zu markieren, werden Variablen und Methoden benötigt, die die Indizes der ersten und letzten Zeilen und Buchstaben des markierten Textes bestimmen. Weiters eine Methode, die diese Werte zurücksetzt, wenn die Markierung aufgehoben wird.
Jedes Mal, wenn eine der Tastenkombinationen gedrückt wird, wird die Methode CTextBox::SetStartSelectedTextIndexes() aufgerufen, bevor der Kursor verschoben wird. Sie bestimmt die Indices der Zeile und des Buchstabens, wo der Textkursor sich gerade befindet. Diese Werte werden nur gesetzt, wenn es der erste Aufruf der Methode ist, nach einer vorherigen Zurücksetzung. Der Kursor wird erst nach dem Aufruf dieser Methode bewegt. Dann wird die Methode CTextBox::SetEndSelectedTextIndexes() aufgerufen, sie bestimmt die Endwerte der Indices der Zeile und des Buchstabens (es sind die der Position des Textkursors). Wenn es sich während des Verschiebens des Textkursors im Markiermodus herausstellt, dass der Kursor sich wieder genau dort befindet, von wo er startete, werden die Werte durch die Methode CTextBox::ResetSelectedText() zurückgesetzt. Die Werte werden auch bei jeder Bewegung des Textkursors, durch das Löschen der Textauswahl oder bei der Deaktivierung des Textfeldes.
class CTextBox : public CElement { private: //--- Die Indices der Zeilen und Buchstaben des Anfangs und Endes (der Textauswahl) int m_selected_line_from; int m_selected_line_to; int m_selected_symbol_from; int m_selected_symbol_to; //--- private: //--- Bestimmen der Indices (1) des Anfangs und (2) Endes der Textauswahl void SetStartSelectedTextIndexes(void); void SetEndSelectedTextIndexes(void); //--- Rücksetzen der Textauswahl void ResetSelectedText(void); }; //+------------------------------------------------------------------+ //| Bestimmen der Startindices für die Textauswahl | //+------------------------------------------------------------------+ void CTextBox::SetStartSelectedTextIndexes(void) { //--- Wenn der die Startindices für die Textauswahl noch nicht bestimmt wurden if(m_selected_line_from==WRONG_VALUE) { m_selected_line_from =(int)m_text_cursor_y_pos; m_selected_symbol_from =(int)m_text_cursor_x_pos; } } //+------------------------------------------------------------------+ //| Bestimmen der Endindices für die Textauswahl | //+------------------------------------------------------------------+ void CTextBox::SetEndSelectedTextIndexes(void) { //--- Bestimmen der Endindices für die Textauswahl m_selected_line_to =(int)m_text_cursor_y_pos; m_selected_symbol_to =(int)m_text_cursor_x_pos; //--- Löschen die Auswahl, wenn alle Indices gleich sind if(m_selected_line_from==m_selected_line_to && m_selected_symbol_from==m_selected_symbol_to) ResetSelectedText(); } //+------------------------------------------------------------------+ //| Rücksetzen der Textauswahl | //+------------------------------------------------------------------+ void CTextBox::ResetSelectedText(void) { m_selected_line_from =WRONG_VALUE; m_selected_line_to =WRONG_VALUE; m_selected_symbol_from =WRONG_VALUE; m_selected_symbol_to =WRONG_VALUE; }
Teile des Codes, die früher von Methoden zur Bewegung des Kursors verwendet wurden, werden jetzt in eine eigene Methode verschoben, da sie für die Textauswahl wiederholt verwendet werden. Dasselbe gilt für den Code zur Anpassung der Bildlaufleisten, falls sich der Textkursor aus dem sichtbaren Teil bewegt.
class CTextBox : public CElement { private: //--- Bewegen des Textkursors um einen Buchstaben nach links void MoveTextCursorToLeft(void); //--- Bewegen des Textkursors um einen Buchstaben nach rechts void MoveTextCursorToRight(void); //--- Bewegen des Textkursors um einen Buchstaben nach oben void MoveTextCursorToUp(void); //--- Bewegen des Textkursors um einen Buchstaben nach unten void MoveTextCursorToDown(void); //--- Anpassen der horizontalen Bildlaufleiste void CorrectingHorizontalScrollThumb(void); //--- Anpassen der vertikalen Bildlaufleiste void CorrectingVerticalScrollThumb(void); };
Alle Methoden, die auf die gedrückten Tastenkombinationen reagieren, (eine von ihnen ist Shift) beinhalten praktisch den gleichen Code, bis auf jene, die den Kursor bewegt. Daher ist eine weitere Methode sinnvoll, der einfach nur die Bewegungsrichtung des Textkursors übergeben wird. Die Enumeration ENUM_MOVE_TEXT_CURSOR mit mehreren Identifikatoren (siehe die Auflistung unten) wurde der Datei Enums.mqh hinzugefügt. Sie kann für die Bewegungsrichtung des Textkursor verwendet werden:
- TO_NEXT_LEFT_SYMBOL — ein Buchstabe nach links.
- TO_NEXT_RIGHT_SYMBOL — ein Buchstabe nach rechts.
- TO_NEXT_LEFT_WORD — ein Wort nach links.
- TO_NEXT_RIGHT_WORD — ein Wort nach rechts.
- TO_NEXT_UP_LINE — eine Zeile nach oben.
- TO_NEXT_DOWN_LINE — eine Zeile nach oben.
- TO_BEGIN_LINE — an den Anfang der aktuellen Zeile.
- TO_END_LINE — an das Ende der aktuellen Zeile.
- TO_BEGIN_FIRST_LINE — an den Anfang der ersten Zeile.
- TO_END_LAST_LINE — an das Ende der letzten Zeile.
//+------------------------------------------------------------------+ //| Enumeration der Bewegungsrichtung des Textkursors | //+------------------------------------------------------------------+ enum ENUM_MOVE_TEXT_CURSOR { TO_NEXT_LEFT_SYMBOL =0, TO_NEXT_RIGHT_SYMBOL =1, TO_NEXT_LEFT_WORD =2, TO_NEXT_RIGHT_WORD =3, TO_NEXT_UP_LINE =4, TO_NEXT_DOWN_LINE =5, TO_BEGIN_LINE =6, TO_END_LINE =7, TO_BEGIN_FIRST_LINE =8, TO_END_LAST_LINE =9 };
Jetzt können wir die allgemeine Methode zur Bewegung des Textkursors erstellen — CTextBox::MoveTextCursor(), ihr muss nur eine der Identifikatoren der oberen Liste übergeben werden. Die gleiche Methode wird in praktisch allen Methoden verwendet, die auf Tastendrücke im Element CTextBox reagieren.
class CTextBox : public CElement { private: //--- Bewegen des Textkursor in die angegebene Richtung void MoveTextCursor(const ENUM_MOVE_TEXT_CURSOR direction); }; //+------------------------------------------------------------------+ //| Bewegen des Textkursor in die angegebene Richtung | //+------------------------------------------------------------------+ void CTextBox::MoveTextCursor(const ENUM_MOVE_TEXT_CURSOR direction) { switch(direction) { //--- Bewegen des Kursors um einen Buchstaben nach links case TO_NEXT_LEFT_SYMBOL : MoveTextCursorToLeft(); break; //--- Bewegen des Kursors um einen Buchstaben nach rechts case TO_NEXT_RIGHT_SYMBOL : MoveTextCursorToRight(); break; //--- Bewegen des Kursors um einen Wort nach links case TO_NEXT_LEFT_WORD : MoveTextCursorToLeft(true); break; //--- Bewegen des Kursors um einen Wort nach rechts case TO_NEXT_RIGHT_WORD : MoveTextCursorToRight(true); break; //--- Bewegen des Kursors um eine Zeile nach oben case TO_NEXT_UP_LINE : MoveTextCursorToUp(); break; //--- Bewegen des Kursors um eine Zeile nach unten case TO_NEXT_DOWN_LINE : MoveTextCursorToDown(); break; //--- Bewegen des Kursors an den Zeilenanfang case TO_BEGIN_LINE : SetTextCursor(0,m_text_cursor_y_pos); break; //--- Bewegen des Kursors an das Zeilenende case TO_END_LINE : { //--- Abfrage der Zeichenzahl der aktuellen Zeile uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol); //--- Bewegen des Kursors SetTextCursor(symbols_total,m_text_cursor_y_pos); break; } //--- Bewegen des Kursors an den Anfang der ersten Zeile case TO_BEGIN_FIRST_LINE : SetTextCursor(0,0); break; //--- Bewegen des Kursors an das Ende der letzten Zeile case TO_END_LAST_LINE : { //--- Ermitteln der Anzahl der Zeilen und Buchstaben der letzten Zeile uint lines_total =::ArraySize(m_lines); uint symbols_total =::ArraySize(m_lines[lines_total-1].m_symbol); //--- Bewegen des Kursors SetTextCursor(symbols_total,lines_total-1); break; } } }
Der Codes dieser Datei kann deutlich reduziert werden, weil die Methoden zur Bewegung des Textkursors und zur Textauswahl viele, sich wiederholende Teile aufweisen.
Beispiel eines sich wiederholenden Codabschnitts in der Methode für das Bewegen des Textkursors:
//+------------------------------------------------------------------+ //| Handhabung der gedrückten Taste Links | //+------------------------------------------------------------------+ bool CTextBox::OnPressedKeyLeft(const long key_code) { //--- Verlassen, wenn (1) es nicht die Taste Links ist oder (2) die Taste Ctrl gedrückt ist oder (3) die Taste Shift gedrückt ist if(key_code!=KEY_LEFT || m_keys.KeyCtrlState() || m_keys.KeyShiftState()) return(false); //--- Rücksetzen der Auswahl ResetSelectedText(); //--- Bewegen des Textkursors um einen Buchstaben nach links MoveTextCursor(TO_NEXT_LEFT_SYMBOL); //--- Anpassen der Bildlaufleiste CorrectingHorizontalScrollThumb(); CorrectingVerticalScrollThumb(); //--- 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); }
Beispiel eines sich wiederholenden Codes in der Methode zur Textauswahl:
//+------------------------------------------------------------------+ //| Zeitgleiches Drücken der Tasten Shift + Links | //+------------------------------------------------------------------+ bool CTextBox::OnPressedKeyShiftAndLeft(const long key_code) { //--- Verlassen, wenn (1) es nicht die Taste Links ist oder (2) die Taste Ctrl gedrückt ist oder (3) die Taste Shift nicht gedrückt ist if(key_code!=KEY_LEFT || m_keys.KeyCtrlState() || !m_keys.KeyShiftState()) return(false); //--- Bestimmen der Startindices für die Textauswahl SetStartSelectedTextIndexes(); //--- Bewegen des Textkursors um einen Buchstaben nach links MoveTextCursor(TO_NEXT_LEFT_SYMBOL); //--- Bestimmen der Endindices für die Textauswahl SetEndSelectedTextIndexes(); //--- Anpassen der Bildlaufleiste CorrectingHorizontalScrollThumb(); CorrectingVerticalScrollThumb(); //--- 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); }
Führen wir noch eine weitere (überladene) Methode CTextBox::MoveTextCursor() ein. Der Methode muss der Identifikator der Kursorbewegung übergeben werden, zusammen mit dem Kennzeichnen, dass es entweder (1) eine Bewegung des Textkursors oder oder (2) eine Textauswahl ist.
class CTextBox : public CElement { private: //--- Bewegen des Textkursor in die angegebene Richtung void MoveTextCursor(const ENUM_MOVE_TEXT_CURSOR direction,const bool with_highlighted_text); }; //+------------------------------------------------------------------+ //| Bewegen des Textkursors in die angegebenen Richtung und | //| mit einer Bedingung | //+------------------------------------------------------------------+ void CTextBox::MoveTextCursor(const ENUM_MOVE_TEXT_CURSOR direction,const bool with_highlighted_text) { //--- Wenn sich nur der Textkursor bewegt if(!with_highlighted_text) { //--- Rücksetzen der Auswahl ResetSelectedText(); //--- Bewegen des Kursors an den Anfang der ersten Zeile MoveTextCursor(direction); } //--- Wenn Text ausgewählt wird else { //--- Bestimmen der Startindices für die Textauswahl SetStartSelectedTextIndexes(); //--- Bewegen des Textkursors um einen Buchstaben nach links MoveTextCursor(direction); //--- Bestimmen der Endindices für die Textauswahl SetEndSelectedTextIndexes(); } //--- Anpassen der Bildlaufleiste CorrectingHorizontalScrollThumb(); CorrectingVerticalScrollThumb(); //--- 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()); }
Die Methoden der Tastenkombinationen, die die Textauswahl betreffen, sind unten gezeigt. Deren Code ist fast identisch (sie unterscheiden sich nur in den Parametern). Daher können Sie den Code in den dem Artikel beigefügten Dateien studieren:
class CTextBox : public CElement { private: //--- Reagieren auf die gedrückten Tasten Shift + Links bool OnPressedKeyShiftAndLeft(const long key_code); //--- Reagieren auf die gedrückten Tasten Shift + Rechts bool OnPressedKeyShiftAndRight(const long key_code); //--- Reagieren auf die gedrückten Tasten Shift + Hoch bool OnPressedKeyShiftAndUp(const long key_code); //--- Reagieren auf die gedrückten Tasten Shift + Runter bool OnPressedKeyShiftAndDown(const long key_code); //--- Reagieren auf die gedrückten Tasten Shift + Home bool OnPressedKeyShiftAndHome(const long key_code); //--- Reagieren auf die gedrückten Tasten Shift + End bool OnPressedKeyShiftAndEnd(const long key_code); //--- Reagieren auf die gedrückten Tasten Ctrl + Shift + Links bool OnPressedKeyCtrlShiftAndLeft(const long key_code); //--- Reagieren auf die gedrückten Tasten Ctrl + Shift + Rechts bool OnPressedKeyCtrlShiftAndRight(const long key_code); //--- Reagieren auf die gedrückten Tasten Ctrl + Shift + Home bool OnPressedKeyCtrlShiftAndHome(const long key_code); //--- Reagieren auf die gedrückten Tasten Ctrl + Shift + End bool OnPressedKeyCtrlShiftAndEnd(const long key_code); };
Bis jetzt wurde nur Text in ganzen Zeilen vor einem Hintergrund behandelt. Da aber die ausgewählten Buchstaben und ihr Hintergrund ihre Farben ändern, muss der Text Buchstabe für Buchstabe ausgegeben werden. Dafür machen wir ein paar Änderungen in der Methode CTextBox::TextOut().
Dazu aber benötigen wir eine weitere Methode CTextBox::CheckSelectedText(), die die ausgewählten Buchstaben überprüft. Wir wissen bereits, dass während der Textauswahl die Indices des Textkursors der Zeilen und Buchstaben des Anfangs und des Endes gesichert werden. Daher ist es einfach zu bestimmen, ob ein Buchstabe einer Zeile ausgewählt wurde oder nicht, einfach durch ein Iteration in einer Schleife über die Buchstaben. Die Logik ist einfach:
- Falls der erste Index der Zeile kleiner ist als der letzte, wird der Buchstabe ausgewählt:
- Wenn dies die letzte Zeile ist und der Buchstabe steht rechts des zuletzt gewählten
- Wenn dies die Anfangszeile ist und der Buchstabe links vom anfänglich gewählten
- Alle Buchstaben der dazwischenliegenden Zeilen werden ausgewählt
- Falls der erste Index der Zeile größer ist als der letzte, wird der Buchstabe ausgewählt:
- Wenn dies die letzte Zeile ist und der Buchstabe steht links des zuletzt gewählten
- Wenn dies die Anfangszeile ist und der Buchstabe rechts vom anfänglich gewählten
- Alle Buchstaben der dazwischenliegenden Zeilen werden ausgewählt
- Befindet sich der ausgewählte Text nur in einer Zeile, wird ein Buchstaben dann gewählt, wenn er zwischen dem ersten und dem letzten Index des Buchstaben liegt.
class CTextBox : public CElement { private: //--- Prüfen der Existenz des ausgewählten Textes bool CheckSelectedText(const uint line_index,const uint symbol_index); }; //+------------------------------------------------------------------+ //| Prüfen der Existenz des ausgewählten Textes | //+------------------------------------------------------------------+ bool CTextBox::CheckSelectedText(const uint line_index,const uint symbol_index) { bool is_selected_text=false; //--- Verlassen, wenn es keine Textauswahl gibt if(m_selected_line_from==WRONG_VALUE) return(false); //--- Wenn der erste Index auf einer Zeile weiter unten ist if(m_selected_line_from>m_selected_line_to) { //--- Die Endzeile und der Buchstabe rechts des zuletzt gewählten ist if((int)line_index==m_selected_line_to && (int)symbol_index>=m_selected_symbol_to) { is_selected_text=true; } //--- Die Anfangszeile und der Buchstabe links vom anfänglich gewählten else if((int)line_index==m_selected_line_from && (int)symbol_index<m_selected_symbol_from) { is_selected_text=true; } //--- Zeilen dazwischen (alle Buchstaben werden ausgewählt) else if((int)line_index>m_selected_line_to && (int)line_index<m_selected_line_from) { is_selected_text=true; } } //--- Wenn der erste Index auf einer Zeile weiter oben ist else if(m_selected_line_from<m_selected_line_to) { //--- Die letzte Zeile und der Buchstabe ist links des zuletzt gewählten if((int)line_index==m_selected_line_to && (int)symbol_index<m_selected_symbol_to) { is_selected_text=true; } //--- Die Anfangszeile und der Buchstabe ist rechts vom anfänglich gewählten else if((int)line_index==m_selected_line_from && (int)symbol_index>=m_selected_symbol_from) { is_selected_text=true; } //--- Zeilen dazwischen (alle Buchstaben werden ausgewählt) else if((int)line_index<m_selected_line_to && (int)line_index>m_selected_line_from) { is_selected_text=true; } } //--- Wenn Anfangs- und Endindex auf derselben Zeile liegen else { //--- Finden der geprüften Zeile if((int)line_index>=m_selected_line_to && (int)line_index<=m_selected_line_from) { //--- Wenn sich der Kursor nach rechts bewegt und der Buchstabe ist innerhalb des gewählten Bereiches if(m_selected_symbol_from>m_selected_symbol_to) { if((int)symbol_index>=m_selected_symbol_to && (int)symbol_index<m_selected_symbol_from) is_selected_text=true; } //--- Wenn sich der Kursor nach links bewegt und der Buchstabe ist innerhalb des gewählten Bereiches else { if((int)symbol_index>=m_selected_symbol_from && (int)symbol_index<m_selected_symbol_to) is_selected_text=true; } } } //--- Rückgabe des Ergebnisses return(is_selected_text); }
In der Methode CTextBox::TextOut(), die den Text ausgibt, muss eine interne Schleife mit einer Iteration über die Buchstaben der Zeile ergänzt werden, anstatt die Zeile als Ganzes auszugeben. Sie bestimmt, ob der überprüfte Buchstabe ausgewählt wurde. Falls der Buchstabe gewählt wurde, wird dessen Farbe bestimmt und ein ausgefülltes Rechteck wird gezeichnet unter dem Buchstaben. Nach all dem wird der Buchstabe selbst ausgegeben.
class CTextBox : public CElement { private: //--- Ausgabe des Textes auf dem Hintergrund void TextOut(void); }; //+------------------------------------------------------------------+ //| Ausgabe des Textes auf dem Hintergrund | //+------------------------------------------------------------------+ void CTextBox::TextOut(void) { //--- Löschen des Hintergrundes m_canvas.Erase(AreaColorCurrent()); //--- Abfrage der Größe des Zeilenarrays uint lines_total=::ArraySize(m_lines); //--- Korrektur, wenn die Größe überschritten wurde m_text_cursor_y_pos=(m_text_cursor_y_pos>=lines_total)? lines_total-1 : 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); //--- Wenn mehrzeilig oder die Anzahl der Zeichen ist größer Null if(m_multi_line_mode || symbols_total>0) { //--- Abfrage der Zeilenbreite int line_width=(int)LineWidth(m_text_cursor_x_pos,m_text_cursor_y_pos); //--- Abfragen der Zeilenhöhe und Iteration über alle Zeilen in einer Schleife int line_height=(int)LineHeight(); 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); //--- Abfragen der Größe der Zeichenkette uint string_length=::ArraySize(m_lines[i].m_symbol); //--- Zeichnen des Textes for(uint s=0; s<string_length; s++) { uint text_color=TextColorCurrent(); //--- Gibt es einen ausgewählten Text, bestimme dessen Farbe und die Hintergrundfarbe des aktuellen Buchstaben if(CheckSelectedText(i,s)) { //--- Farbe des gewählten Textes text_color=::ColorToARGB(m_selected_text_color); //--- Berechnung der Koordinaten für das Zeichnen des Hintergrunds int x2=x+m_lines[i].m_width[s]; int y2=y+line_height-1; //--- Zeichnen der Hintergrundfarbe des Zeichens m_canvas.FillRectangle(x,y,x2,y2,::ColorToARGB(m_selected_back_color)); } //--- Zeichnen des Buchstabens m_canvas.TextOut(x,y,m_lines[i].m_symbol[s],text_color,TA_LEFT); //--- X-Koordinate des nächsten Buchstabens x+=m_lines[i].m_width[s]; } } } //--- Wenn nicht mehrzeilig und keine Zeichen angegeben, wird der Standardtext angezeigt else { //--- Textausgabe, 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); } }
Die Methoden zur Textauswahl wurden beschrieben, und so verhält sich jetzt die fertige Programm:
Fig. 6. Demonstration einer Textauswahl in einem Textfeld eines MQL-Programms.
Methoden zum Löschen des ausgewählten Textes
Kommen wir nun zu den Methoden zum Löschen des ausgewählten Textes. Hier ist es wichtig zu beachten, dass verschiedene Methoden zum Löschen des Textes verwendet werden, je nachdem, ob nur eine oder mehrere Zeilen ausgewählt wurden.
Die Methode CTextBox::DeleteTextOnOneLine() wird aufgerufen, wenn nur eine einzelne Zeile gelöscht werden soll. Die Anzahl der zu löschenden Buchstaben wird zu Beginn der Methode festgelegt. Dann, wenn der Anfangsindex des Buchstabens des gewählten Textes rechts liegt, werden die Buchstaben um die Anzahl der zu löschenden Buchstaben nach rechts verschoben, ausgehend von der Anfangsposition. Danach wird der Array der Buchstaben der Zeile um die gleiche Zahl reduziert.
Falls der Index des Buchstabens am Anfang der Textauswahl links ist, muss auch der Textkursor um die Anzahl der zu löschenden Buchstaben nach rechts verschoben werden.
class CTextBox : public CElement { private: //--- Löschen der Textauswahl in der Zeile void DeleteTextOnOneLine(void); }; //+------------------------------------------------------------------+ //| Löschen der Textauswahl in der Zeile | //+------------------------------------------------------------------+ void CTextBox::DeleteTextOnOneLine(void) { int symbols_total =::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol); int symbols_to_delete =::fabs(m_selected_symbol_from-m_selected_symbol_to); //--- Wenn der Anfangsindex des Buchstabens rechts ist if(m_selected_symbol_to<m_selected_symbol_from) { //--- Verschieben der Zeichen in den freien Bereich der aktuellen Zeile MoveSymbols(m_text_cursor_y_pos,m_selected_symbol_from,m_selected_symbol_to); } //--- Wenn der Anfangsindex des Buchstabens links ist else { //--- Verschieben des Textkursors um die Anzahl der zu löschenden Buchstaben nach links m_text_cursor_x_pos-=symbols_to_delete; //--- Verschieben der Zeichen in den freien Bereich der aktuellen Zeile MoveSymbols(m_text_cursor_y_pos,m_selected_symbol_to,m_selected_symbol_from); } //--- Verringern der Arraygröße der aktuellen Zeile um die Zahl der zu verschiebenden Zeichen ArraysResize(m_text_cursor_y_pos,symbols_total-symbols_to_delete); }
Die Methode CTextBox::DeleteTextOnMultipleLines() wird zum Löschen mehrerer Zeilen der Textauswahl verwendet. Ihr Algorithmus ist etwas komplizierter. Zuerst muss bestimmt werden:
- Die Gesamtzahl der Buchstaben der Anfangs- und der Endzeile
- Die Zahl der Zeilen der Textauswahl (ohne die Anfangs- und die Endzeile)
- Die Zahl der zu löschenden Buchstaben der Anfangs- und der Endzeile.
Die weiteren Schritten sind unten angeführt. Abhängig von der Richtung der Textauswahl (hoch oder runter), werden der Anfangs- und der Endindex jeweils anderen Methoden übergeben.
- Die nicht zu löschenden Buchstaben, die von einer Zeile in einer anderen verschoben werden müssen, werden in ein temporäres, dynamisches Array kopiert.
- Das empfangende Array (Zeile) wird neu dimensioniert.
- Die Daten werden dem Array mit der Struktur der Zeilen hinzugefügt.
- Die Zeilen werden um die Anzahl der zu löschenden Zeilen verschoben.
- Der Array der Zeilen wird neu dimensioniert (verringert um die Anzahl der zu löschenden Zeilen).
- Falls die Anfangszeile über der Endzeile liegt (die Textauswahl erfolgte abwärts), wird der Textkursor auf die Anfangsindices (Zeile und Buchstabe) der Textauswahl gesetzt.
class CTextBox : public CElement { private: //--- Löschen der Textauswahl mit mehreren Zeilen void DeleteTextOnMultipleLines(void); }; //+------------------------------------------------------------------+ //| Löschen der Textauswahl mit mehreren Zeilen | //+------------------------------------------------------------------+ void CTextBox::DeleteTextOnMultipleLines(void) { //--- Die Gesamtzahl der Buchstaben der Anfangs- und der Endzeile uint symbols_total_line_from =::ArraySize(m_lines[m_selected_line_from].m_symbol); uint symbols_total_line_to =::ArraySize(m_lines[m_selected_line_to].m_symbol); //--- Die zu löschenden Zeilen dazwischen uint lines_to_delete =::fabs(m_selected_line_from-m_selected_line_to); //--- Die zu löschenden Buchstabenzahl auf der ersten und letzten Zeile uint symbols_to_delete_in_line_from =::fabs(symbols_total_line_from-m_selected_symbol_from); uint symbols_to_delete_in_line_to =::fabs(symbols_total_line_to-m_selected_symbol_to); //--- Falls die Anfangszeile unter der Endzeile liegt if(m_selected_line_from>m_selected_line_to) { //--- Kopieren der zu verschiebenen Zeichen in das Array string array[]; CopyWrapSymbols(m_selected_line_from,m_selected_symbol_from,symbols_to_delete_in_line_from,array); //--- Neu dimensionieren der Empfängerzeile uint new_size=m_selected_symbol_to+symbols_to_delete_in_line_from; ArraysResize(m_selected_line_to,new_size); //--- Hinzufügen der Daten zum Array der Struktur der Zeilen PasteWrapSymbols(m_selected_line_to,m_selected_symbol_to,array); //--- Abfrage der Größe des Zeilenarrays uint lines_total=::ArraySize(m_lines); //--- Verschieben nach oben um die Zahl der zu löschenden Zeilen MoveLines(m_selected_line_to+1,lines_total-lines_to_delete,lines_to_delete,false); //--- Größenanpassung des Zeilenarrays ::ArrayResize(m_lines,lines_total-lines_to_delete); } //--- Falls die Anfangszeile über der Endzeile liegt else { //--- Kopieren der zu verschiebenen Zeichen in das Array string array[]; CopyWrapSymbols(m_selected_line_to,m_selected_symbol_to,symbols_to_delete_in_line_to,array); //--- Neu dimensionieren der Empfängerzeile uint new_size=m_selected_symbol_from+symbols_to_delete_in_line_to; ArraysResize(m_selected_line_from,new_size); //--- Hinzufügen der Daten zum Array der Struktur der Zeilen PasteWrapSymbols(m_selected_line_from,m_selected_symbol_from,array); //--- Abfrage der Größe des Zeilenarrays uint lines_total=::ArraySize(m_lines); //--- Verschieben nach oben um die Zahl der zu löschenden Zeilen MoveLines(m_selected_line_from+1,lines_total-lines_to_delete,lines_to_delete,false); //--- Größenanpassung des Zeilenarrays ::ArrayResize(m_lines,lines_total-lines_to_delete); //--- Verschieben des Kursors an die Anfangsposition der Auswahl SetTextCursor(m_selected_symbol_from,m_selected_line_from); } }
Welche der Methoden zum Löschen des Textes aufgerufen wird, geschieht in der Hauptmethode — CTextBox::DeleteSelectedText(). Ist die Textauswahl gelöscht, werden die Werte der Anfangs- und Endindizes zurückgesetzt. Danach muss die Größe des Textfeldes neu berechnet werden, da sich die Zeilenzahl geändert haben könnte. Die maximale Breite der Zeilen, die für die Berechnung des Textfeldes herangezogen werden, könnte sich auch geändert haben. Zuletzt sendet die Methode eine Nachricht, dass der Textkursor verschoben wurde. Die Methode gibt ein true zurück, wenn Text ausgewählt und gelöscht wurde. Falls kein Text ausgewählt worden war, als die Methode aufgerufen wurde, gibt sie false zurück.
class CTextBox : public CElement { private: //--- Löschen der Textauswahl void DeleteSelectedText(void); }; //+------------------------------------------------------------------+ //| Löschen der Textauswahl | //+------------------------------------------------------------------+ bool CTextBox::DeleteSelectedText(void) { //--- Verlassen, wenn kein Text ausgewählt wurde if(m_selected_line_from==WRONG_VALUE) return(false); //--- Falls nur Buchstaben einer Zeile zu löschen sind if(m_selected_line_from==m_selected_line_to) DeleteTextOnOneLine(); //--- Falls Buchstaben mehrerer Zeilen zu löschen sind else DeleteTextOnMultipleLines(); //--- Rücksetzen der Textauswahl ResetSelectedText(); //--- Berechne die Größe des Textfeldes CalculateTextBoxSize(); //--- Setze die neue Größe des Textfeldes ChangeTextBoxSize(); //--- Anpassen der Bildlaufleiste CorrectingHorizontalScrollThumb(); CorrectingVerticalScrollThumb(); //--- 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 Methode CTextBox::DeleteSelectedText() wird nicht nur aufgerufen, wenn die Taste Backspace gedrückt wurde, sondern auch: (1) wenn ein neuer Buchstabe eingegeben wurde und (2) wenn die Taste Enter gedrückt wurde. In diesem Fall wird zuerst der Text gelöscht und dann die Aktion gemäß der gedrückten Taste ausgeführt.
So sieht es in der fertigen Anwendung aus:
Fig. 7. Demonstration des Löschens einer Textauswahl.
Klasse für das Arbeiten mit Bilddaten
Als Ergänzung in diesem Artikel betrachten wir eine neue Klasse (CImage) für die Arbeit mit Bilddaten. Sie wird wiederholt in vielen Klassen der Bibliothek verwendet, wenn sie Bilder zeichnen sollen. Die Klasse befindet sich in der Datei Objects.mqh.
Die Eigenschaften der Klasse:- Array der Pixel des Bildes;
- Bildbreite;
- Bildhöhe;
- Pfad zu Bilddatei.
//+------------------------------------------------------------------+ //| Klasse zum Sichern der Bilddaten | //+------------------------------------------------------------------+ class CImage { protected: uint m_image_data[]; // Array of the image pixels uint m_image_width; // Image width uint m_image_height; // Image height string m_bmp_path; // Path to the image file public: //--- (1) Größe des Datenarrays, (2) Bestimmen/Rückgabe der Daten (Pixelfarbe) uint DataTotal(void) { return(::ArraySize(m_image_data)); } uint Data(const uint data_index) { return(m_image_data[data_index]); } void Data(const uint data_index,const uint data) { m_image_data[data_index]=data; } //--- Bestimmen/Rückgabe der Bildbreite void Width(const uint width) { m_image_width=width; } uint Width(void) { return(m_image_width); } //--- Bestimmen/Rückgabe der Bildhöhe void Height(const uint height) { m_image_height=height; } uint Height(void) { return(m_image_height); } //--- Bestimmen/Rückgabe des Pfads zur Bilddatei void BmpPath(const string bmp_file_path) { m_bmp_path=bmp_file_path; } string BmpPath(void) { return(m_bmp_path); } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CImage::CImage(void) : m_image_width(0), m_image_height(0), m_bmp_path("") { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CImage::~CImage(void) { }
Die Methode CImage::ReadImageData() soll das Bild mit seinen Eigenschaften sichern. Sie lädt das Bild des gegebenen Pfads und sichert dessen Daten.
class CImage { public: //--- Lesen und sichern der Daten des übergebenen Bildes bool ReadImageData(const string bmp_file_path); }; //+------------------------------------------------------------------+ //| Sichern des übergebenen Bildes in einen Array | //+------------------------------------------------------------------+ bool CImage::ReadImageData(const string bmp_file_path) { //--- Rücksetzen der Fehlervariablen ::ResetLastError(); //--- Sichern des Pfads zur Bilddatei m_bmp_file_path=bmp_file_path; //--- Lesen und Sichern der Daten des Bildes if(!::ResourceReadImage(m_bmp_file_path,m_image_data,m_image_width,m_image_height)) { ::Print(__FUNCTION__," > error: ",::GetLastError()); return(false); } //--- return(true); }
Manchmal könnte es notwendig sein, eine Kopie eines Bildes des gleichen Typs (CImage) zu erstellen. Das erledigt die Methode CImage::CopyImageData(). Zu Beginn der Methode wird das Empfängerarray auf die Größe des Quellarrays gesetzt. Dann werden in einer Schleife die Daten des Quellarrays in das Empfängerarray kopiert.
class CImage { public: //--- Kopieren der Daten des übergebenen Bildes void CopyImageData(CImage &array_source); }; //+------------------------------------------------------------------+ //| Kopieren der Daten des übergebenen Bildes | //+------------------------------------------------------------------+ void CImage::CopyImageData(CImage &array_source) { //--- Abfragen der Größe des Empfänger- und des Quellarrays uint data_total =DataTotal(); uint source_data_total =::GetPointer(array_source).DataTotal(); //--- Größenänderung des Empfängerarrays ::ArrayResize(m_image_data,source_data_total); //--- Kopieren der Daten for(uint i=0; i<source_data_total; i++) m_image_data[i]=::GetPointer(array_source).Data(i); }
Vor dieser Aktualisierung verwendete die Klasse CCanvasTable eine Struktur, um Bilddaten zu sichern. Jetzt nach der Einführung der Klasse CImage wurden die entsprechenden Änderungen vollzogen.
Schlussfolgerung
Dieser Artikel beschließt die Entwicklung eines mehrzeiligen Texteingabefeldes. Dessen Schlüsselfunktion ist, dass es jetzt keine Beschränkungen bezüglich der Zahl der eingegebenen Buchstaben und Zeilen mehr gibt, ein Manko der standardmäßigen Grafikobjekte des Typs OBJ_EDIT. Im nächsten Artikel werden wir das Thema "Elemente in Tabellenzellen" weiterentwickeln, mit der Möglichkeit, Werte der Tabellenzellen zu ändern, und das mit den Elementen aus diesem Artikel. Darüber hinaus werden mehrere Elemente in einen neuen Modus überführt: Sie werden übergeben und nicht aus mehreren standardmäßigen Grafikobjekt erstellt.
Zur Zeit schaut das Schema der Bibliothek zum Erstellen einer grafische Benutzeroberfläche wie folgt aus:
Fig. 8. Struktur der Bibliothek im augenblicklichen Entwicklungsstand.
Unten könne Sie die letzten Versionen der Bibliothek und der Dateien zum Testen herunterladen, wie sie hier in diesem Artikel beschrieben wurden.
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/3197
- 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.