Grafisches Interface XI: Texteingabefelder und Kombinationsfelder in Tabellenzellen (build 15)
Inhalt
- Einführung
- Ändern der Fenstergröße
- Text- und Kombinationsfelder in Tabellenzellen
- Testanwendung
- Schlussfolgerung
Einführung
Der erste Artikel Grafische Interfaces I: Vorbereitung der Bibliotheksstruktur (Kapitel 1) beschreibt im Detail den Zweck der Bibliothek. Die Vollversion der Bibliothek im aktuellen Entwicklungszustand befindet sich immer am Ende eines jeden Artikels dieser Serie. Die Dateien müssen in die gleichen Verzeichnisse wie im beigefügten Archiv kopiert werden.
Die nächste Aktualisierung konzentriert sich auf die Tabellensteuerelemente (die Klasse CTable). Vorher wird es möglich Ankreuzkästchen und Schaltflächen zu Tabellenzellen hinzuzufügen. Erweitern wir die Palette dieser Steuerelemente mit Text- und Kombinationsfeldern. Die neue Version kann jetzt auch während der Laufzeit der Anwendung die Fenstergröße ändern.
Ändern der Fenstergröße
Um die Verwendung von Listen, Tabellen oder mehrzeiligen Textfeldern zu erleichtern, ist es oft notwendig, das Fenster auf das gesamte Chart auszudehnen oder die Größe zu verkleinern. Es gibt mehrere Wege die Fenstergröße zu ändern.
- Ein Modus zum schnellen Umschalten von Normal- auf Vollbild und zurück mit einem einzigen Klick auf eine spezielle Taste.
- Ein Doppelklick auf den Fenstertitel vergrößert das Fenster ebenfalls auf Vollbild. Ein erneuter Doppelklick bringt das Fenster wieder in den vorherigen Zustand.
- Eine Größenänderung durch das Ziehen des Fensterrahmens mit der linken Maustaste.
Schauen wir uns an, wie das in der Bibliothek realisiert wurde.
Für die Taste für den Vollbildmodus wurde eine eigene Instanz der Klasse CButton deklariert. Die 'public' Methode CButton::GetFullscreenButtonPointer() soll den Pointer auf die Taste übernehmen. Standardmäßig ist diese Taste deaktiviert. Sie wird mit der Methode CButton::FullscreenButtonIsUsed() aktiviert.
//+------------------------------------------------------------------+ //| Anzeigenklasse für Steuerelemente | //+------------------------------------------------------------------+ class CWindow : public CElement { private: //--- Objekte dieser Anzeige CButton m_button_fullscreen; //--- Vorhandensein der Taste zur Fenstermaximierung auf den Vollbildmodus bool m_fullscreen_button; //--- public: //--- Rückgabe des Pointers auf die Taste CButton *GetFullscreenButtonPointer(void) { return(::GetPointer(m_button_fullscreen)); } //--- Verwendung der Taste für den Vollbildmodus void FullscreenButtonIsUsed(const bool state) { m_fullscreen_button=state; } bool FullscreenButtonIsUsed(void) const { return(m_fullscreen_button); } };
Die Taste für den Vollbildmodus wird in der allgemeinen Methode CWindow::CreateButtons() erzeugt (siehe den Code unten), wo alle aktivierten Tasten der Klasse erzeugt werden. Ähnlich wie bei den Tasten zum Anzeigen von Tooltips und des Minimieren der Anzeige kann die Taste für den Vollbildmodus nur im Hauptfenster verwendet werden.
//+------------------------------------------------------------------+ //| Erstellen von Tasten für die Anzeigen | //+------------------------------------------------------------------+ #resource "\\Images\\EasyAndFastGUI\\Controls\\full_screen.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\minimize_to_window.bmp" //--- bool CWindow::CreateButtons(void) { //--- Verlassen, wenn das Programm ein Skript ist if(CElementBase::ProgramType()==PROGRAM_SCRIPT) return(true); //--- Zähler, die Größe, Anzahl int i=0,x_size=20; int buttons_total=4; //--- Der Pfad zu der Datei string icon_file=""; //--- Ausnahme im Erfassungsbereich m_right_limit=0; //--- CButton *button_obj=NULL; //--- for(int b=0; b<buttons_total; b++) { ... else if(b==1) { m_button_fullscreen.MainPointer(this); //--- Verlassen, wenn 1. Taste oder 2. das Dialogfeld deaktiviert sind if(!m_fullscreen_button || m_window_type==W_DIALOG) continue; //--- button_obj=::GetPointer(m_button_fullscreen); icon_file="Images\\EasyAndFastGUI\\Controls\\full_screen.bmp"; } ... } //--- return(true); }
Die Mindestgröße des Fensters wird automatisch auf die beim Erstellen des Steuerelements angegebene Größe gesetzt. Aber diese Werte können überschrieben werden. Aktuelle ist es aber nicht möglich des Fenster auf eine Größe kleiner als 200x200 Pixel zu setzen. Dies wird in der Methode zur Initialisierung der Eigenschaften des Steuerelements gesteuert — CWindow::InitializeProperties().
class CWindow : public CElement { private: //--- Mindestgröße des Fensters int m_minimum_x_size; int m_minimum_y_size; //--- public: //--- Bestimmen der Mindestgröße des Fensters void MinimumXSize(const int x_size) { m_minimum_x_size=x_size; } void MinimumYSize(const int y_size) { m_minimum_y_size=y_size; } }; //+------------------------------------------------------------------+ //| Konstruktor | //+------------------------------------------------------------------+ CWindow::CWindow(void) : m_minimum_x_size(0), m_minimum_y_size(0) { ... } //+------------------------------------------------------------------+ //| Initialisierung der Eigenschaften | //+------------------------------------------------------------------+ void CWindow::InitializeProperties(const long chart_id,const int subwin,const string caption_text,const int x_gap,const int y_gap) { ... m_x_size =(m_x_size<1)? 200 : m_x_size; m_y_size =(m_y_size<1)? 200 : m_y_size; ... m_minimum_x_size =(m_minimum_x_size<200)? m_x_size : m_minimum_x_size; m_minimum_y_size =(m_minimum_y_size<200)? m_y_size : m_minimum_y_size; ... }
Vor dem Vergrößern des Fenster auf das Vollbild, ist es notwendig, die aktuellen Maße, Koordinaten und den Modus der automatischen Größenänderung zu speichern, falls diese festgelegt wurden. Diese Werte werden in besonderen privaten Feldern der Klasse gespeichert:
class CWindow : public CElement { private: //--- Die letzten Koordinaten und Maße vor dem Umschalten in das Vollbild int m_last_x; int m_last_y; int m_last_x_size; int m_last_y_size; bool m_last_auto_xresize; bool m_last_auto_yresize; };
Die Methode CWindow::OnClickFullScreenButton() reagiert auf den Klick der Taste für das Vollbild. Sie prüft zuerst Identifikator und Index des Steuerelements, dann wird der Code in zwei Blöcke aufgeteilt:
- Wenn das Fenster im Augenblick nicht maximiert ist, schalten Sie es in den Vollbildmodus um. Als nächstes werden die aktuellen Maße ermittelt, und die aktuellen Größen, Koordinaten des Fensters und die automatischen Größenänderungen in den speziellen Feldern der Klasse gespeichert. Da im Vollbildmodus die Größe der Anzeige automatisch angepasst werden muss, wenn sich die Größe des Hauptcharts ändert, muss die automatische Größenanpassung aktiviert werden. Danach werden die Fenstergrößen eingestellt. Gleichzeitig wird die Position des Fensters in die linke obere Ecke gesetzt, so dass das gesamte Chart ausfüllt wird. Das Symbol in der Taste wird durch ein anderes ersetzt.
- Ist das Fenster gerade maximiert, schaltet es auf die vorherige Fenstergröße zurück. Jetzt wechselt auch die automatische Größenanpassung in den vorherigen Modus zurück. Je nach dem welcher Modus deaktiviert ist, wird die vorherigen Fenstergröße eingestellt. Auch die vorige Position, und das entsprechende Symbol für die Taste werden festgelegt.
Am Ende der Methode CWindow::OnClickFullScreenButton() wird die Anzeige neu gezeichnet:
class CWindow : public CElement { private: //--- Status des Fensters im Vollbild bool m_is_fullscreen; //--- public: //--- Wechseln zum Vollbild oder zur vorherigen Fenstergröße bool OnClickFullScreenButton(const int id=WRONG_VALUE,const int index=WRONG_VALUE); }; //+------------------------------------------------------------------+ //| Wechseln zum Vollbild oder zur vorherigen Fenstergröße | //+------------------------------------------------------------------+ bool CWindow::OnClickFullScreenButton(const int id=WRONG_VALUE,const int index=WRONG_VALUE) { //--- Prüfen der Identifikatoren und Indices des Steuerelements bei einem externen Aufruf int check_id =(id!=WRONG_VALUE)? id : CElementBase::Id(); int check_index =(index!=WRONG_VALUE)? index : CElementBase::Index(); //--- Verlassen, wenn die Indices nicht übereinstimmen if(check_id!=m_button_fullscreen.Id() || check_index!=m_button_fullscreen.Index()) return(false); //--- Falls das Fenster nicht im Vollbild ist if(!m_is_fullscreen) { //--- Wechseln zum Vollbild m_is_fullscreen=true; //--- Abfrage der aktuellen Maße des Chartfensters SetWindowProperties(); //--- Sichern der aktuellen Koordinaten und Maße der Anzeige m_last_x =m_x; m_last_y =m_y; m_last_x_size =m_x_size; m_last_y_size =m_full_height; m_last_auto_xresize =m_auto_xresize_mode; m_last_auto_yresize =m_auto_yresize_mode; //--- Aktivieren der autom. Größenanpassung der Anzeige m_auto_xresize_mode=true; m_auto_yresize_mode=true; //--- Vergrößern der Anzeige auf den ganze Chartfenster ChangeWindowWidth(m_chart.WidthInPixels()-2); ChangeWindowHeight(m_chart.HeightInPixels(m_subwin)-3); //--- Aktualisieren der Lage m_x=m_y=1; Moving(m_x,m_y); //--- Ersetzen des Symbols der Taste m_button_fullscreen.IconFile("Images\\EasyAndFastGUI\\Controls\\minimize_to_window.bmp"); m_button_fullscreen.IconFileLocked("Images\\EasyAndFastGUI\\Controls\\minimize_to_window.bmp"); } //--- Falls das Fenster im Vollbild ist else { //--- Wechseln zur vorherigen Fenstergröße m_is_fullscreen=false; //--- Deaktivieren der autom. Größenanpassung m_auto_xresize_mode=m_last_auto_xresize; m_auto_yresize_mode=m_last_auto_yresize; //--- Wenn der Modus deaktiviert ist, einstellen der vorherigen Größe if(!m_auto_xresize_mode) ChangeWindowWidth(m_last_x_size); if(!m_auto_yresize_mode) ChangeWindowHeight(m_last_y_size); //--- Aktualisieren der Lage m_x=m_last_x; m_y=m_last_y; Moving(m_x,m_y); //--- Ersetzen des Symbols der Taste m_button_fullscreen.IconFile("Images\\EasyAndFastGUI\\Controls\\full_screen.bmp"); m_button_fullscreen.IconFileLocked("Images\\EasyAndFastGUI\\Controls\\full_screen.bmp"); } //--- Entfernen des Fokus von der Taste m_button_fullscreen.MouseFocus(false); m_button_fullscreen.Update(true); return(true); }
Die Methode CWindow::OnClickFullScreenButton() wird in der Ereignisbehandlung des Steuerelements aufgerufen, wenn das Ereignis ON_CLICK_BUTTON auftritt.
//+------------------------------------------------------------------+ //| Ereignisbehandlung des Charts | //+------------------------------------------------------------------+ void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Reagieren auf den Klick auf die Taste if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { ... //--- Prüfen des Vollbildmodus if(OnClickFullScreenButton((uint)lparam,(uint)dparam)) return; ... //--- return; } }
Das Ergebnis sieh dann so aus:
Fig. 1. Demonstration des Umschaltens auf Vollbild und zurück.
Um durch einen Doppelklick auf den Fenstertitel in den Vollbildmodus und zurück zu wechseln, genügt es nun, dieses Doppelklickereignis (ON_DOUBLE_CLICK) auf den Fenstertitel in der Ereignisbehandlung des Steuerelements zu verarbeiten.
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Ereignisbehandlung eines Doppelklicks auf ein Objekt if(id==CHARTEVENT_CUSTOM+ON_DOUBLE_CLICK) { //--- Wenn das Ereignis in der Leiste des Fenstertitels geschah if(CursorInsideCaption(m_mouse.X(),m_mouse.Y())) OnClickFullScreenButton(m_button_fullscreen.Id(),m_button_fullscreen.Index()); //--- return; } }
Und so schaut alles aus:
Fig. 2. Demonstration des Umschaltens auf Vollbild durch einen Doppelklick auf die Titelleiste.
Kommen wir nun zur Größenänderung durch das Ziehen des Fensterrahmens. Die Methode CWindow::ResizeMode() ermöglicht das.
class CWindow : public CElement { private: //--- Modus der Größenänderung des Fensters bool m_xy_resize_mode; //--- public: //--- Möglichkeit zur Größenänderung des Fensters bool ResizeMode(void) const { return(m_xy_resize_mode); } void ResizeMode(const bool state) { m_xy_resize_mode=state; } }; //+------------------------------------------------------------------+ //| Konstruktor | //+------------------------------------------------------------------+ CWindow::CWindow(void) : m_xy_resize_mode(false) { ... }
Um den Klick mit der linken Maustaste auf die Fensterränder zu erfassen, ist ein weiterer Identifikator (PRESSED_INSIDE_BORDER) in der ENUM_MOUSE_STATE Enumeration erforderlich, die sich in der Datei Enums.mqh befindet.
//+------------------------------------------------------------------+ //| Enums.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Enumeration der Bereiche für die linke Maustaste | //+------------------------------------------------------------------+ enum ENUM_MOUSE_STATE { NOT_PRESSED =0, PRESSED_INSIDE =1, PRESSED_OUTSIDE =2, PRESSED_INSIDE_HEADER =3, PRESSED_INSIDE_BORDER =4 };
Ist der Modus zur Größenänderung aktiviert, wird für den Mauszeiger ein grafisches Objekt mit dem neuen Identifikator MP_WINDOW_RESIZE aus der Enumeration ENUM_MOUSE_POINTER erzeugt.
//+------------------------------------------------------------------+ //| Enumeration der Pointertypen | //+------------------------------------------------------------------+ enum ENUM_MOUSE_POINTER { MP_CUSTOM =0, MP_X_RESIZE =1, MP_Y_RESIZE =2, MP_XY1_RESIZE =3, MP_XY2_RESIZE =4, MP_WINDOW_RESIZE =5, MP_X_RESIZE_RELATIVE =6, MP_Y_RESIZE_RELATIVE =7, MP_X_SCROLL =8, MP_Y_SCROLL =9, MP_TEXT_SELECT =10 };
Die Kasse CWindow wurde um die Methode CreateResizePointer() erweitert, um das grafische Objekt für den Mauszeiger zu erzeugen:
class CWindow : public CElement { private: bool CreateResizePointer(void); }; //+------------------------------------------------------------------+ //| Erstellen des Mauskursors für die Größenänderung | //+------------------------------------------------------------------+ bool CWindow::CreateResizePointer(void) { //--- Verlassen, wenn der Modus zur Größenänderung deaktiviert ist if(!m_xy_resize_mode) return(true); //--- Eigenschaften m_xy_resize.XGap(13); m_xy_resize.YGap(11); m_xy_resize.XSize(23); m_xy_resize.YSize(23); m_xy_resize.Id(CElementBase::Id()); m_xy_resize.Type(MP_WINDOW_RESIZE); //--- Erstellen der Textfelder if(!m_xy_resize.CreatePointer(m_chart_id,m_subwin)) return(false); //--- return(true); }
Es war notwendig, mehrere Methoden zur Größenänderung des Fensters zu implementieren. Betrachten wir sie der Reihe nach.
Die Position des Mauszeigers muss verfolgt werden, wenn er im Fensterbereich erscheint. In dieser Version kann die Größe des Fensters durch Ziehen des linken, rechten oder unteren Rahmens verändert werden. Die Methode CWindow:: ResizeModeIndex() verfolgt den Fokus über einen der aufgelisteten Rahmen und speichert dessen Index zur späteren Behandlung in anderen Methoden. Die Koordinaten des Mauskursors relativ zum Fenster werden dieser Methode für weitere Berechnungen übergeben.
class CWindow : public CElement { private: //--- Der Index des Rahmens für die Größenänderung des Fensters int m_resize_mode_index; //--- private: //--- Rückgabe des Index des Modus für die Größenänderung des Fensters int ResizeModeIndex(const int x,const int y); }; //+------------------------------------------------------------------+ //| Rückgabe des Index des Modus für die Größenänderung des Fensters | //+------------------------------------------------------------------+ int CWindow::ResizeModeIndex(const int x,const int y) { //--- Rückgabe des Index des Modus, wenn bereits gezogen wird if(m_resize_mode_index!=WRONG_VALUE && m_mouse.LeftButtonState()) return(m_resize_mode_index); //--- Breite, Abstand und Index des Rahmens int width =5; int offset =15; int index =WRONG_VALUE; //--- Prüfen des Fokus auf den linken Rahmen if(x>0 && x<width && y>m_caption_height+offset && y<m_y_size-offset) index=0; //--- Prüfen des Fokus auf den rechten Rahmen else if(x>m_x_size-width && x<m_x_size && y>m_caption_height+offset && y<m_y_size-offset) index=1; //--- Prüfen des Fokus auf den unteren Rahmen else if(y>m_y_size-width && y<m_y_size && x>offset && x<m_x_size-offset) index=2; //--- Wenn der erhaltene Index den geklickten Bereich betrifft if(index!=WRONG_VALUE) m_clamping_area_mouse=PRESSED_INSIDE_BORDER; //--- Rückgabe des Index des Bereiches return(index); }
Es werden weitere Variablen in der Klasse benötigt: für die Bestimmung der Erfassungspunkte, für die Speicherung der Anfangsmaße und für spätere Berechnungen. Sobald die Größenänderung beginnt, muss eine Meldung zur Bildung der Liste der verfügbaren Steuerelemente erzeugt werden. Daher wird auch eine Methode benötigt, die die Nachricht erstellt, das Steuerelement wiederherstellt und die Variablen zurücksetzt: CWindow::ZeroResizeVariables().
class CWindow : public CElement { private: //--- Variablen der Größenänderung des Fensters int m_x_fixed; int m_size_fixed; int m_point_fixed; //--- private: //--- Nullstellen der Variablen void ZeroResizeVariables(void); }; //+------------------------------------------------------------------+ //| Rücksetzten der Variablen der Größenänderung des Fensters | //+------------------------------------------------------------------+ void CWindow::ZeroResizeVariables(void) { //--- Verlassen, wenn bereits auf Null gestellt if(m_point_fixed<1) return; //--- Null m_x_fixed =0; m_size_fixed =0; m_point_fixed =0; //--- Senden einer Nachricht, um die verfügbaren Steuerelemente wiederherzustellen ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),1,""); //--- Senden einer Nachricht über die Veränderung des grafischen Interfaces ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,""); }
Die Methode CWindow::CheckResizePointer() wurde implementiert, um festzustellen, ob der Mauszeiger zum Anzeigen und Ausblenden des Fensters bereit ist. Hier wird die Methode CWindow::ResizeModeIndex() verwendet, um den Index des Rahmens zu bestimmen.
Wenn der Mauszeiger noch nicht angezeigt wird, muss man bei einem bestimmten Rahmenindex das entsprechende Symbol setzen, die Position anpassen und den Pointer ausgeben.
Falls der ermittelte Rahmenindex ergibt, dass der Mauszeiger bereits angezeigt wird, wird er zum Mauszeiger verschoben, wenn der Fokus auf einem der Rahmen liegt. Wenn kein Fokus vorhanden ist und die linke Maustaste losgelassen wird, wird der Cursor ausgeblendet und die Variablen auf Null gesetzt.
Die Methode CWindow::CheckResizePointer() gibt true zurück, wenn der Fensterrahmen für eine Größenänderung definiert ist, ansonsten false.
class CWindow : public CElement { private: //--- Prüfen der Bereitschaft für die Größenänderung des Fensters bool CheckResizePointer(const int x,const int y); }; //+------------------------------------------------------------------+ //| Prüfen der Bereitschaft für die Größenänderung des Fensters | //+------------------------------------------------------------------+ bool CWindow::CheckResizePointer(const int x,const int y) { //--- Bestimmen des aktuellen Rahmenindex m_resize_mode_index=ResizeModeIndex(x,y); //--- Falls der Kursor ausgeblendet ist if(!m_xy_resize.IsVisible()) { //--- Falls der Rahmen definiert istr if(m_resize_mode_index!=WRONG_VALUE) { //--- Zur Bestimmung des Index des angezeigten Symbols des Mauskursors int index=WRONG_VALUE; //--- Falls auf einem senkrechten Rahmen if(m_resize_mode_index==0 || m_resize_mode_index==1) index=0; //--- Falls auf einem waagerechten Rahmen else if(m_resize_mode_index==2) index=1; //--- Wechseln des Symbols m_xy_resize.ChangeImage(0,index); //--- Verschieben, Neuzeichnen und Anzeigen m_xy_resize.Moving(m_mouse.X(),m_mouse.Y()); m_xy_resize.Update(true); m_xy_resize.Reset(); return(true); } } else { //--- Verschieben des Kursors if(m_resize_mode_index!=WRONG_VALUE) m_xy_resize.Moving(m_mouse.X(),m_mouse.Y()); //--- Ausblenden des Kursors else if(!m_mouse.LeftButtonState()) { //--- Ausblenden des Kursors und Rücksetzen der Variablen m_xy_resize.Hide(); ZeroResizeVariables(); } //--- Neuzeichnen des Charts m_chart.Redraw(); return(true); } //--- return(false); }
Die Methode CWindow::CheckDragWindowBorder() wird verwendet, um den Beginn des Ziehens eines Fensterrahmens zu überprüfen. Sobald der Rahmen gezogen wird, müssen die aktuellen Maße und die Koordinate des ersten Zielpunkts in den Variablen der Klasse gespeichert werden. Gleichzeitig wird eine Meldung zur Ermittlung der verfügbaren Steuerelemente gesendet.
Wenn die nachfolgenden Aufrufe dieser Methode zeigen, dass der Rahmen bereits gezogen wird, dann muss die in diesem Modus zurückgelegte Strecke berechnet und der resultierenden Wert zurückzugeben werden.
class CWindow : public CElement { private: //--- Prüfen des Ziehens des Fensterrahmens int CheckDragWindowBorder(const int x,const int y); }; //+------------------------------------------------------------------+ //| Prüfen des Ziehens des Fensterrahmens | //+------------------------------------------------------------------+ int CWindow::CheckDragWindowBorder(const int x,const int y) { //--- Bestimmen der zurückgelegten Strecke int distance=0; //--- Falls das Ziehen des Rahmen noch nicht bekannt ist if(m_point_fixed<1) { //--- Falls entlang der X-Achse die Größe geändert wird if(m_resize_mode_index==0 || m_resize_mode_index==1) { m_x_fixed =m_x; m_size_fixed =m_x_size; m_point_fixed =x; } //--- Falls entlang der Y-Achse die Größe geändert wird else if(m_resize_mode_index==2) { m_size_fixed =m_y_size; m_point_fixed =y; } //--- Senden eine Nachricht der verfügbaren Steuerelemente ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),0,""); //--- Senden einer Nachricht über die Veränderung des grafischen Interfaces ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,""); return(0); } //--- Falls es der linke Rahmen ist if(m_resize_mode_index==0) distance=m_mouse.X()-m_x_fixed; //--- Falls es der rechte Rahmen ist else if(m_resize_mode_index==1) distance=x-m_point_fixed; //--- Falls es der untere Rahmen ist else if(m_resize_mode_index==2) distance=y-m_point_fixed; //--- Rückgabe der zurückgelegten Strecke return(distance); }
Das von der Methode CWindow::CheckDragWindowBorder() zurückgegebene Ergebnis wird an die Methode CWindow::CalculateAndResizeWindow() übergeben, die die Fensterkoordinaten und -dimensionen relativ zu ihrem Rahmen berechnet.
class CWindow : public CElement { private: //--- Berechnen und Ändern der Fenstergröße void CalculateAndResizeWindow(const int distance); }; //+------------------------------------------------------------------+ //| Berechnen und Ändern der Fenstergröße | //+------------------------------------------------------------------+ void CWindow::CalculateAndResizeWindow(const int distance) { //--- Linker Rahmen if(m_resize_mode_index==0) { int new_x =m_x_fixed+distance-m_point_fixed; int new_x_size =m_size_fixed-distance+m_point_fixed; //--- Verlassen, wenn die Grenzen überschritten wurden if(new_x<1 || new_x_size<=m_minimum_x_size) return; //--- Koordinaten CElementBase::X(new_x); m_canvas.X_Distance(new_x); //--- Setzen und sichern der Größe CElementBase::XSize(new_x_size); m_canvas.XSize(new_x_size); m_canvas.Resize(new_x_size,m_canvas.YSize()); } //--- Rechter Rahmen else if(m_resize_mode_index==1) { int gap_x2 =m_chart_width-m_mouse.X()-(m_size_fixed-m_point_fixed); int new_x_size =m_size_fixed+distance; //--- Verlassen, wenn die Grenzen überschritten wurden if(gap_x2<1 || new_x_size<=m_minimum_x_size) return; //--- Setzen und sichern der Größe CElementBase::XSize(new_x_size); m_canvas.XSize(new_x_size); m_canvas.Resize(new_x_size,m_canvas.YSize()); } //--- Unterer Rahmen else if(m_resize_mode_index==2) { int gap_y2=m_chart_height-m_mouse.Y()-(m_size_fixed-m_point_fixed); int new_y_size=m_size_fixed+distance; //--- Verlassen, wenn die Grenzen überschritten wurden if(gap_y2<2 || new_y_size<=m_minimum_y_size) return; //--- Setzen und sichern der Größe m_full_height=new_y_size; CElementBase::YSize(new_y_size); m_canvas.YSize(new_y_size); m_canvas.Resize(m_canvas.XSize(),new_y_size); } }
Die Methoden CWindow::CheckDragWindowBorder() und CWindow::CheckDragWindowBorder() werden innerhalb der Methode CWindow::UpdateSize() aufgerufen. Hier wird am Anfang der Methode geprüft, ob die linke Maustaste gedrückt ist. Wenn die Taste losgelassen wird, werden alle Werte der Variablen, die sich auf die Änderung der Fenstergröße beziehen, zurückgesetzt und das Programm verlässt die Methode.
Wird die linke Maustaste gedrückt, dann muss 1. der Abstand bestimmt werden, den der gezogene Rahmen zurückgelegt hat, 2. das Fenster berechnet und verändert werden, 3. das Fenster neu gezeichnet und 4. die Position seiner Elemente angepasst werden.
Am Ende der Methode wird je nach der Achse, auf der das Fenster verändert wurde, ein Ereignis erzeugt, das später für die Größenänderung aller Steuerelemente verwendet wird, die dem Fenster zugeordnet sind und deren entsprechender Modus aktiviert wurde.
class CWindow : public CElement { private: //--- Aktualisierung der Fenstergrößen void UpdateSize(const int x,const int y); }; //+------------------------------------------------------------------+ //| Aktualisierung der Fenstergrößen | //+------------------------------------------------------------------+ void CWindow::UpdateSize(const int x,const int y) { //--- Falls beendet und die linke Maustaste losgelassen, Rücksetzen der Werte if(!m_mouse.LeftButtonState()) { ZeroResizeVariables(); return; } //--- Verlassen, wenn Erfassen und Verschieben des Rahmens noch nicht begonnen hat int distance=0; if((distance=CheckDragWindowBorder(x,y))==0) return; //--- Berechnen und Ändern der Fenstergröße CalculateAndResizeWindow(distance); //--- Neuzeichnen des Fensters Update(true); //--- Aktualisieren der Position der Objekte Moving(m_x,m_y); //--- Erstellen einer Nachricht, dass die Fenstergröße verändert wurde if(m_resize_mode_index==2) ::EventChartCustom(m_chart_id,ON_WINDOW_CHANGE_YSIZE,(long)CElementBase::Id(),0,""); else ::EventChartCustom(m_chart_id,ON_WINDOW_CHANGE_XSIZE,(long)CElementBase::Id(),0,""); }
Alle aufgeführten Methoden zur Messung der Fenstermaße werden in der Hauptmethode CWindow::ResizeWindow() aufgerufen. Sie prüft zunächst, ob das Fenster verfügbar ist. Wenn die linke Maustaste dann nicht über einen der Fensterrahmen gedrückt wurde, verlässt das Programm die Methode. Dann folgen drei weitere Überprüfungen: 1. wurde der Modus zur Größenänderung aktiviert, 2. ist das Fenster im Vollbild und 3. ist es nicht minimiert.
Wenn alle Prüfungen bestanden worden sind, dann werden die relativen Koordinaten des Mauskursors ermittelt, es wird der Fensterrahmen erfasst, und es wird das Steuerelement verkleinert.
class CWindow : public CElement { private: //--- Steuert die Fenstergröße void ResizeWindow(void); }; //+------------------------------------------------------------------+ //| Steuert die Fenstergröße | //+------------------------------------------------------------------+ void CWindow::ResizeWindow(void) { //--- Verlassen, wenn das Fenster nicht verfügbar ist if(!IsAvailable()) return; //--- Verlassen, wenn die Maustaste nicht über der Anzeige gedrückt wurde if(m_clamping_area_mouse!=PRESSED_INSIDE_BORDER && m_clamping_area_mouse!=NOT_PRESSED) return; //--- Verlassen, wenn 1. der Modus zur Änderung der Fenstergröße deaktiviert oder // 2. das Fenster im Vollbildmodus oder 3. das Fenster minimiert ist. if(!m_xy_resize_mode || m_is_fullscreen || m_is_minimized) return; //--- Koordinaten int x =m_mouse.RelativeX(m_canvas); int y =m_mouse.RelativeY(m_canvas); //--- Prüfen der Bereitschaft zur Veränderungen der Breite von Listen if(!CheckResizePointer(x,y)) return; //--- Aktualisierung der Fenstergrößen UpdateSize(x,y); }
Die Methode CWindow::ResizeWindow() wird in der Ereignisbehandlung aufgerufen, wenn das Ereignis einer Bewegung des Mauskursors auftritt (CHARTEVENT_MOUSE_MOVE).
Und so schaut alles aus:
Fig. 3. Demonstration der Größenänderung des Fensters durch das Ziehen des Rahmens.
Text- und Kombinationsfelder in Tabellenzellen
Wenn Tabellenzellen unterschiedliche Steuerelemente haben, wird die Tabelle zu einem sehr flexiblen Werkzeug zur Verwaltung der darin enthaltenen Daten. Die nächstliegendsten Beispiele sind direkt im Handelsterminal des MetaTraders zu sehen. Es sind dies die Fenster für die Einstellungen der Eingabeparameter eines Programms für den Chart und für den Strategietester des Terminals. Grafische Interfaces mit solchen Fähigkeiten bringen MQL-Anwendungen auf ein neues Niveau.
Fig. 4. Fenster der Einstellungen eines MQL-Programms.
Fig. 5. Fenster der Einstellungen eines MQL-Programms für den Strategietester.
Einer der vorherigen Artikel ermöglicht Ankreuzkästchen und Schaltflächen in Tabellenzellen. Betrachten wir nun die Implementierung von Texteditoren und Kombinationsfeldern.
Zuerst wurden in der Datei Enum.mqh der Enumeration ENUM_TYPE_CELL zwei neue Identifikatoren hinzugefügt, um die Typen der Tabellenzellen zu kennzeichnen:
- CELL_COMBOBOX – Eine Zelle des Typs Kombinationsfeld.
- CELL_EDIT – Eine Zelle des Typs Texteingabefeld.
//+------------------------------------------------------------------+ //| Enumeration der Typen der Tabellenzellen | //+------------------------------------------------------------------+ enum ENUM_TYPE_CELL { CELL_SIMPLE =0, CELL_BUTTON =1, CELL_CHECKBOX =2, CELL_COMBOBOX =3, CELL_EDIT =4 };
Zur Umsetzung des Geplanten genügt es, in der Tabelle nur ein Texteingabefeld (CTextEdit) und/oder ein Kombinationsfeld (CComboBox) als Bestandteil des Steuerelements CTable zu erzeugen. Sie erscheinen beim Doppelklick auf eine Zelle, wenn deren Wert geändert werden soll.
Bei der Einstellung des Zelltyps mit der Methode CTable::CellType() muss einmal der Merker in den Extravariablen der Klasse gesetzt werden, wenn der Typ CELL_EDIT oder CELL_COMBOBOX angegeben ist.
//+------------------------------------------------------------------+ //| Kasse des Erzeugens eine Tabellendarstellung | //+------------------------------------------------------------------+ class CTable : public CElement { private: //--- Vorhandensein von Tabellenzellen mit Texteditoren und Kombinationsfeldern bool m_edit_state; bool m_combobox_state; //--- public: //--- Bestimmen/Abfragen des Zelltyps void CellType(const uint column_index,const uint row_index,const ENUM_TYPE_CELL type); }; //+------------------------------------------------------------------+ //| Bestimmen des Zelltyps | //+------------------------------------------------------------------+ void CTable::CellType(const uint column_index,const uint row_index,const ENUM_TYPE_CELL type) { //--- Prüfen der Arraygrenze if(!CheckOutOfRange(column_index,row_index)) return; //--- Bestimmen des Zelltyps m_columns[column_index].m_rows[row_index].m_type=type; //--- Vorzeichen eines Texteingabefeldes if(type==CELL_EDIT && !m_edit_state) m_edit_state=true; //--- Vorzeichen eines Kombinationsfeldes else if(type==CELL_COMBOBOX && !m_combobox_state) m_combobox_state=true; }
Wenn sich beim Anlegen einer Tabelle herausstellt, dass bei keiner Zelle CELL_EDIT oder CELL_COMBOBOX gesetzt wurden, werden die Steuerelemente der entsprechenden Typen nicht erzeugt. Bei Bedarf können die Pointer auf diese Steuerelemente abgefragt werden.
class CTable : public CElement { private: //--- Objekte für das Erstellen einer Tabelle CTextEdit m_edit; CComboBox m_combobox; //--- private: bool CreateEdit(void); bool CreateCombobox(void); //--- public: //--- Rückgabe des Pointers auf das Steuerelement CTextEdit *GetTextEditPointer(void) { return(::GetPointer(m_edit)); } CComboBox *GetComboboxPointer(void) { return(::GetPointer(m_combobox)); } };
Bei einem Doppelklick auf die Tabelle wird die Methode CTable::CheckCellElement() aufgerufen. Sie enthält die entsprechenden Ergänzungen für die Zellen der Typen CELL_EDIT und CELL_COMBOBOX. Der Code unten zeigt verkürzt diese Methode. Die Methoden zur Handhabung verschiedener Zelltypen werden im Folgenden detailliert beschrieben.
//+------------------------------------------------------------------+ //| Prüfen, ob das Steuerelement beim Klick aktiv ist | //+------------------------------------------------------------------+ bool CTable::CheckCellElement(const int column_index,const int row_index,const bool double_click=false) { //--- Verlassen, wenn die Zelle gar kein Steuerelement hat if(m_columns[column_index].m_rows[row_index].m_type==CELL_SIMPLE) return(false); //--- switch(m_columns[column_index].m_rows[row_index].m_type) { ... //--- Falls dies eine Zelle mit einem Texteingabefeld ist case CELL_EDIT : { if(!CheckPressedEdit(column_index,row_index,double_click)) return(false); //--- break; } //--- Falls dies eine Zelle mit einem Kombinationsfeld ist case CELL_COMBOBOX : { if(!CheckPressedCombobox(column_index,row_index,double_click)) return(false); //--- break; } } //--- return(true); }
Bevor wir nun mit der Betrachtung der Methoden zur Handhabung von Klicks auf Tabellenzellen mit Steuerelementen fortfahren, soll noch auf die Ergänzungen der Steuerelemente vom Typ CTextBox eingegangen werden. Manchmal ist es notwendig, dass der gesamte Text in einem Textfeld automatisch ausgewählt wird, wenn das Textfeld aktiviert wird, und der Textkursor an das Zeilenende bewegt wird. Dies ist praktisch, um ganze Textteile schnell einzugeben und ersetzen zu können.
Die automatische Textauswahl in der aktuellen Version funktioniert nur für ein einzeiliges Textfeld. Dieser Modus kann mit der Methode CTextBox::AutoSelectionMode() aktiviert werden.
//+------------------------------------------------------------------+ //| Klasse zur Erstellung eines mehrzeiligen Textfeldes | //+------------------------------------------------------------------+ class CTextBox : public CElement { private: //--- Modus zur automatischen Textauswahl bool m_auto_selection_mode; //--- public: //--- Modus zur automatischen Textauswahl void AutoSelectionMode(const bool state) { m_auto_selection_mode=state; } };
Die 'private' Methode CTextBox::SelectAllText() wurde implementiert, um den ganzen Text eines Textfeldes auszuwählen. Hier wird zunächst die Anzahl der Zeichen der ersten Zeile ermittelt und die Indizes für die Textauswahl gesetzt. Als nächstes muss der sichtbare Textbereich ganz nach rechts verschoben werden. Zum Schluss muss der Textkursor an das Zeilenende bewegt werden.
class CTextBox : public CElement { private: //--- Auswählen des ganzen Textes void SelectAllText(void); }; //+------------------------------------------------------------------+ //| Auswählen des ganzen Textes | //+------------------------------------------------------------------+ void CTextBox::SelectAllText(void) { //--- Abfrage der Größe des Arrays mit den Zeichen int symbols_total=::ArraySize(m_lines[0].m_symbol); //--- Setzen des Index des ausgewählten Textes m_selected_line_from =0; m_selected_line_to =0; m_selected_symbol_from =0; m_selected_symbol_to =symbols_total; //--- Verschieben des Schiebereglers der horizontalen Bildlaufleiste an die letzte Position HorizontalScrolling(); //--- Verschieben des Kursor an das Ende der Zeile SetTextCursor(symbols_total,0); }
Das Eingabefeld erscheint nach einem Doppelklick auf eine Tabellenzelle. Aber um einen weiteren Klick zum Aktivieren des Textfeldes zu vermeiden, wird eine zusätzliche 'public' Methode CTextBox::ActivateTextBox() benötigt. Ein Aufruf simuliert einen Klick auf das Textfeld. Dazu wird einfach die Methode CTextBox::OnClickTextBox() aufgerufen und ihr der Namen des grafischen Objekts des Steuerelementes übergeben. Die Textauswahl erfolgt durch diese Methode.
class CTextBox : public CElement { public: //--- Aktiviere das Textfeld void ActivateTextBox(void); }; //+------------------------------------------------------------------+ //| Aktiviere das Textfeld | //+------------------------------------------------------------------+ void CTextBox::ActivateTextBox(void) { OnClickTextBox(m_textbox.Name()); }
Da nur ein einziges Textfeld für die gesamte Tabelle verwendet wird, muss die Möglichkeit bestehen, die Größe der Tabelle ändern können, da die Zellen unterschiedliche Breiten haben können. Daher wurde eine zusätzliche 'public' Methode CTextBox::ChangeSize() eingeführt, die die zuvor implementierten Methoden aufruft, die in anderen Artikeln beschrieben wurden.
class CTextBox : public CElement { public: //--- Größenänderung void ChangeSize(const uint x_size,const uint y_size); }; //+------------------------------------------------------------------+ //| Größenänderung | //+------------------------------------------------------------------+ void CTextBox::ChangeSize(const uint x_size,const uint y_size) { //--- Festlegen der neuen Größe ChangeMainSize(x_size,y_size); //--- Berechnen der Größe des Textfeldes CalculateTextBoxSize(); //--- Setzen der neuen Größe des Textfeldes ChangeTextBoxSize(); }
Ein Doppelklick auf eine Zelle mit einem Textfeld ruft die Methode CTable::CheckPressedEdit() auf. Auch hier werden Klassenvariablen zum Speichern der Indizes der zuletzt bearbeiteten Zelle benötigt, um auf das Ereignis des Endes der Werteingabe (ON_END_EDIT) reagieren zu können.
In der aktuellen Version wird das Texteingabefeld nur durch einen Doppelklick auf eine Zelle aufgerufen. Daher gibt es zu Beginn der Methode eine solche Prüfung. Anschließend werden die übergebenen Spalten- und Zeilenindizes gesichert. Um das Texteingabefeld korrekt über der Tabellenzelle zu platzieren, müssen die Koordinaten unter Berücksichtigung des Tabellenversatzes entlang der beiden Achsen berechnet werden. Zusätzlich wird das Vorhandensein von Überschriften in den Berechnungen berücksichtigt. Danach muss die Größe des Textfeldes berechnet, festgelegt und die aktuelle Zeichenfolge eingefügt werden, das in der Zelle angezeigt werden soll. Danach wird das Textfeld aktiviert, angezeigt, und das Diagramm neu gezeichnet, um die letzten Änderungen darzustellen.
class CTable : public CElement { private: //--- Indices der Spalten und Zeilen der letzten, bearbeiteten Zellen int m_last_edit_row_index; int m_last_edit_column_index; //--- private: //--- Prüfen, ob der Klick auf eine Zelle mit einem Textfeld erfolgte bool CheckPressedEdit(const int column_index,const int row_index,const bool double_click=false); }; //+------------------------------------------------------------------+ //| Prüfen, ob der Klick auf eine Zelle mit einem Textfeld erfolgte | //+------------------------------------------------------------------+ bool CTable::CheckPressedEdit(const int column_index,const int row_index,const bool double_click=false) { //--- Verlassen, wenn es kein Doppelklick war if(!double_click) return(false); //--- Speichern der Indices m_last_edit_row_index =row_index; m_last_edit_column_index =column_index; //--- Verschieben entlang der Achsen int x_offset=(int)m_table.GetInteger(OBJPROP_XOFFSET); int y_offset=(int)m_table.GetInteger(OBJPROP_YOFFSET); //--- Setzen der neuen Koordinaten m_edit.XGap(m_columns[column_index].m_x-x_offset); m_edit.YGap(m_rows[row_index].m_y+((m_show_headers)? m_header_y_size : 0)-y_offset); //--- Größe int x_size =m_columns[column_index].m_x2-m_columns[column_index].m_x+1; int y_size =m_cell_y_size+1; //--- Setzen der Größe m_edit.GetTextBoxPointer().ChangeSize(x_size,y_size); //--- Setzen der Werte aus der Tabellenzelle m_edit.SetValue(m_columns[column_index].m_rows[row_index].m_full_text); //--- Aktiviere das Textfeld m_edit.GetTextBoxPointer().ActivateTextBox(); //--- Setzen des Fokus m_edit.GetTextBoxPointer().MouseFocus(true); //--- Anzeigen des Textfeldes m_edit.Reset(); //--- Neuzeichnen des Charts m_chart.Redraw(); return(true); }
Nach der Eingabe des Wertes in die Zelle wird ein Ereignis mit dem Identifikator ON_END_EDIT erzeugt, das in der Ereignisbehandlung der Tabelle empfangen werden muss. Die Methode CTable::OnEndEditCell() wurde implementiert, um auf diesen Vorgang zu reagieren. Sind Zellen mit Textfeldern vorhanden, und deren Identifikatoren stimmen überein, wird der neue Wert dieser Tabellenzelle gesetzt. Danach muss das Textfeld deaktiviert und ausgeblendet werden.
class CTable : public CElement { private: //--- Bearbeitung des Endes der Werteeingabe in eine Zelle bool OnEndEditCell(const int id); }; //+------------------------------------------------------------------+ //| Ereignisbehandlung | //+------------------------------------------------------------------+ void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { ... //--- Bearbeitung des Endes der Werteeingabe if(id==CHARTEVENT_CUSTOM+ON_END_EDIT) { if(OnEndEditCell((int)lparam)) return; //--- return; } ... } //+------------------------------------------------------------------+ //| Bearbeitung des Endes der Werteeingabe in eine Zelle | //+------------------------------------------------------------------+ bool CTable::OnEndEditCell(const int id) { //--- Verlassen, wenn 1. die Identifikatoren nicht übereinstimmen oder 2. es keine Zellen mit Textfeldern gibt if(id!=CElementBase::Id() || !m_edit_state) return(false); //--- Setzen des Wertes der Tabellenzelle SetValue(m_last_edit_column_index,m_last_edit_row_index,m_edit.GetValue(),0,true); Update(); //--- Deaktivieren und Ausblenden des Textfeldes m_edit.GetTextBoxPointer().DeactivateTextBox(); m_edit.Hide(); m_chart.Redraw(); return(true); }
Ein Klick außerhalb des aktivierten Textfeldes sollte dieses Textfeld ausblenden. Dafür wird die Methode CTable::OnEndEditCell() benötigt. Zusätzlich muss das Textfeld deaktiviert werden, damit es beim nächsten Aufruf korrekt angezeigt wird. Die Methode CTable::OnEndEditCell() wird durch die Ereignisbehandlung der Tabelle aufgerufen, sobald das Ereignis eines geänderten Status der linken Maustaste (ON_CHANGE_MOUSE_LEFT_BUTTON) auftritt. Das gleiche Prinzip wird von der Methode CTable::CheckAndHideCombobox() verwendet, wenn es in der Zelle ein Kombinationsfeld gibt. Der Code dieser Methode wird hier nicht aufgeführt, da er mit dem bereits beschrieben praktisch ident ist.
class CTable : public CElement { private: //--- Prüfen, ob das Steuerelement der Zelle ausgeblendet ist void CheckAndHideEdit(void); }; //+------------------------------------------------------------------+ //| Ereignisbehandlung | //+------------------------------------------------------------------+ void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { ... //--- Zustandsänderung der linken Maustaste if(id==CHARTEVENT_CUSTOM+ON_CHANGE_MOUSE_LEFT_BUTTON) { ... //--- Prüfen, ob das Textfeld in der Zelle ausgeblendet ist CheckAndHideEdit(); //--- Prüfen, ob das Kombinationsfeld in der Zelle ausgeblendet ist CheckAndHideCombobox(); return; } ... } //+------------------------------------------------------------------+ //| Prüfen, ob das Textfeld in der Zelle ausgeblendet ist | //+------------------------------------------------------------------+ void CTable::CheckAndHideEdit(void) { //--- Verlassen, wenn es 1. kein Textfeld oder wenn es 2. ausgeblendet ist if(!m_edit_state || !m_edit.IsVisible()) return; //--- Prüfen des Fokus m_edit.GetTextBoxPointer().CheckMouseFocus(); //--- Deaktivieren und Ausblenden des Textfeldes, wenn 1. es außerhalb des Fokus ist und 1. die linke Maustaste gedrückt wurde if(!m_edit.GetTextBoxPointer().MouseFocus() && m_mouse.LeftButtonState()) { m_edit.GetTextBoxPointer().DeactivateTextBox(); m_edit.Hide(); m_chart.Redraw(); } }
Betrachten wir nun, wie die Methoden zum Aufruf des Kombinationsfeldes einer Tabellenzelle funktionieren. Für Zellen des Typs CELL_COMBOBOX wird ein Array zum Speichern der Werte der Kombinationsfeldliste sowie ein zusätzliche Variable zum Speichern des Index des ausgewählten Eintrags benötigt. Der Array und die Variable wurden der Struktur CTCell hinzugefügt.
class CTable : public CElement { private: //--- Eigenschaften der Tabellenzellen struct CTCell { ... string m_value_list[]; // Array of values (for cells with combo boxes) int m_selected_item; // Selected item in the combo box list ... }; };
Wenn der Typ des Kombinationsfeldes (CELL_COMBOBOX) für eine Zelle in der benutzerdefinierten Klasse vor dem Anlegen der Tabelle angegeben wird, muss auch die Liste der Werte an die Kombinationsfeldliste übergeben werden.
Das geschieht durch die Methode CTable::AddValueList(). Diese Methode wird auch der Zellenindex und der Index des in der Kombinationsfeldliste des gewählten Eintrags übergeben. Standardmäßig ist der erste Eintrag ausgewählt (Index 0).
Am Anfang der Methode wird die Einhaltung der Arraygrenzen überprüft. Danach wird der Array in der Struktur CTCell auf die gleiche Größe wie das übergebene Array gesetzt und eine Kopie der Werte erstellt. Der Index des ausgewählte Elementes wird bei Überschreitung der Arraygrenzen angepasst und ebenfalls in der Struktur CTCell gespeichert. Der Text des markierten Elements wird in der Zelle gespeichert.
class CTable : public CElement { public: //--- Hinzufügen einer Liste zum Kombinationsfeld void AddValueList(const uint column_index,const uint row_index,const string &array[],const uint selected_item=0); }; //+------------------------------------------------------------------+ //| Hinzufügen einer Liste zum Kombinationsfeld | //+------------------------------------------------------------------+ void CTable::AddValueList(const uint column_index,const uint row_index,const string &array[],const uint selected_item=0) { //--- Prüfen der Arraygrenze if(!CheckOutOfRange(column_index,row_index)) return; //--- Setzen der Listengröße der angegebenen Zelle uint total=::ArraySize(array); ::ArrayResize(m_columns[column_index].m_rows[row_index].m_value_list,total); //--- Speichen der übergebenen Werte ::ArrayCopy(m_columns[column_index].m_rows[row_index].m_value_list,array); //--- Prüfen des Index des gewählten Elementes in der Liste uint check_item_index=(selected_item>=total)? total-1 : selected_item; //--- Speichern des gewählten Elementes in der Liste m_columns[column_index].m_rows[row_index].m_selected_item=(int)check_item_index; //--- Speichern des Textes des gewählten Elementes in der Zelle m_columns[column_index].m_rows[row_index].m_full_text=array[check_item_index]; }
Die Methode CTable::CheckPressedCombobox() bearbeitet den Doppelklick auf eine Zelle mit einem Kombinationsfeld. Hier werden die Indices der Zellen zunächst für die weitere Verarbeitung gespeichert, falls ein Listeneintrag selektiert wird. Anschließend werden die Koordinaten des Kombinationsfeldes relativ zur linken oberen Ecke der Zelle gesetzt. Danach werden die Steuerelemente auf die Größe der Zelle eingestellt. Um die Größe des Buttons (CButton) und der Liste (CListView) während der Laufzeit zu ändern, wurde die Methode ChangeSize() in ihre Klassen und zwei weitere Variablen aufgenommen. Da die Größe der Liste von Zelle zu Zelle variieren kann, muss die Liste jedes Mal neu aufgebaut und ausgefüllt werden. Anschließend werden die Elemente des Kombinationsfeldes neu gezeichnet und sichtbar gemacht. Ganz am Ende der Methode wird ein Ereignis über Änderungen des grafischen Interfaces erzeugt.
class CTable : public CElement { private: //--- Prüfen, ob Zelle mit Kombinationsfeld geklickt wurde bool CheckPressedCombobox(const int column_index,const int row_index,const bool double_click=false); }; //+------------------------------------------------------------------+ //| Prüfen, ob Zelle mit Kombinationsfeld geklickt wurde | //+------------------------------------------------------------------+ bool CTable::CheckPressedCombobox(const int column_index,const int row_index,const bool double_click=false) { //--- Verlassen, wenn es kein Doppelklick war if(!double_click) return(false); //--- Speichern der Indices m_last_edit_row_index =row_index; m_last_edit_column_index =column_index; //--- Verschieben entlang der Achsen int x_offset=(int)m_table.GetInteger(OBJPROP_XOFFSET); int y_offset=(int)m_table.GetInteger(OBJPROP_YOFFSET); //--- Setzen der neuen Koordinaten m_combobox.XGap(m_columns[column_index].m_x-x_offset); m_combobox.YGap(m_rows[row_index].m_y+((m_show_headers)? m_header_y_size : 0)-y_offset); //--- Setzen der Tastengröße int x_size =m_columns[column_index].m_x2-m_columns[column_index].m_x+1; int y_size =m_cell_y_size+1; m_combobox.GetButtonPointer().ChangeSize(x_size,y_size); //--- Setzen der Listengröße y_size=m_combobox.GetListViewPointer().YSize(); m_combobox.GetListViewPointer().ChangeSize(x_size,y_size); //--- Setzen der Größe der Zellliste int total=::ArraySize(m_columns[column_index].m_rows[row_index].m_value_list); m_combobox.GetListViewPointer().Rebuilding(total); //--- Setzen der Liste der Zelle for(int i=0; i<total; i++) m_combobox.GetListViewPointer().SetValue(i,m_columns[column_index].m_rows[row_index].m_value_list[i]); //--- Setzen des Elementes der Zelle int index=m_columns[column_index].m_rows[row_index].m_selected_item; m_combobox.SelectItem(index); //--- Aktualisieren des Steuerelementes m_combobox.GetButtonPointer().MouseFocus(true); m_combobox.GetButtonPointer().Update(true); m_combobox.GetListViewPointer().Update(true); //--- Anzeigen des Textfeldes m_combobox.Reset(); //--- Neuzeichnen des Charts m_chart.Redraw(); //--- Senden einer Nachricht über die Veränderung des grafischen Interfaces ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,""); return(true); }
Das Ereignis der Auswahl eines Elementes aus Kombinationsfeldliste (ON_CLICK_COMBOBOX_ITEM) wird von der Methode CTable::OnClickComboboxItem() bearbeitet. Hier wird zunächst geprüft, ob die Identifikatoren übereinstimmen und ob ein Kombinationsfeld in der Tabelle vorhanden ist. Sind diese Prüfungen abgeschlossen, werden der Index des ausgewählten Elementes und der Wert des Elementes in die Zelle entsprechend der vorher gespeicherten Index geschrieben.
class CTable : public CElement { private: //--- Behandlung der Auswahl eines Elementes in der Liste der Zelle bool OnClickComboboxItem(const int id); }; //+------------------------------------------------------------------+ //| Ereignisbehandlung | //+------------------------------------------------------------------+ void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { ... //--- Auswählen eines Elementes in der Liste der Zelle if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM) { if(OnClickComboboxItem((int)lparam)) return; //--- return; } ... } //+------------------------------------------------------------------+ //| Auswählen eines Elementes des Kombinationsfeldes der Zelle | //+------------------------------------------------------------------+ bool CTable::OnClickComboboxItem(const int id) { //--- Verlassen, 1. wenn die Identifikator nicht übereinstimmen oder 2. wenn es keine Zellen mit Kombinationsfeldern gibt if(id!=CElementBase::Id() || !m_combobox_state) return(false); //--- Indices der zuletzt bearbeiteten Zelle int c=m_last_edit_column_index; int r=m_last_edit_row_index; //--- Sichern des Index des in der Zelle gewählten Elementes m_columns[c].m_rows[r].m_selected_item=m_combobox.GetListViewPointer().SelectedItemIndex(); //--- Setzen des Wertes der Tabellenzelle SetValue(c,r,m_combobox.GetValue(),0,true); Update(); return(true); }
Am Ende wird dann alles so aussehen:
Fig. 6. Demonstration der Arbeit mit Textfeldern und Kombinationsfeldern in Tabellenzellen.
Testanwendung
Zu Testzwecken wurde eine MQL-Anwendung mit der Tabelle (CTable) und dem mehrzeilige Textfeld (CTextBox) erstellt. In der ersten Spalte der Tabelle enthalten alle Zellen ein Ankreuzkästchen (CELL_CHECKBOX). In der zweiten Spalte der Tabelle haben die Zellen "Textfelder" (CELL_EDIT). In der dritten Spalte haben die Zellen abwechselnd "Kombinationsfelder" (CELL_COMBOBOX) und "Textfelder" (CELL_EDIT). In der fünften Spalte haben die Zellen Tasten (CELL_BUTTON). Die Ereignisbehandlung der benutzerdefinierten Klasse der MQL-Anwendung verarbeitet die Ereignisse und übergibt sie dem mehrzeiligen Textfeld zur Anzeige.
Und so schaut alles aus:
Fig. 7. MQL-Anwendung für die Prüfung der beschriebenen Aufgaben.
Diese Anwendung befinden sich im Archiv am Ende des Artikels für weitere Untersuchung.
Schlussfolgerung
Die Tabelle kann nun Zellen mit dem Typ "Textfeld" und "Kombinationsfeld" erzeugen. Die Anzeige des Steuerelementes jetzt mit der Maus in den Vollbildmodus vergrößert oder auf eine andere Größe manuell verändert werden.
Das allgemeine Schema der Bibliothek im gegenwärtigen Entwicklungsstadium:
Fig. 8. Die Struktur der Bibliothek im aktuellen Zustand der Entwicklung
Der vorgestellte Code ist kostenfrei. Sie können ihn in Ihren, auch kommerziellen, Projekten verwenden, damit Artikel schreiben und ihn für eigene Aufträge verwenden.
Wenn Sie Fragen zur Verwendung des Materials aus dem Artikel haben, können Sie diese im Kommentarteil stellen.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/3394
- 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.