Grafische Interfaces VII: Die Tabellen Controls (Kapitel 1)
Inhalt
- Einleitung
- Das Text-Label Tabellen-Control
- Das Edit-Box Tabellen-Control
- Das Rendered Tabellen-Control
- Schlussfolgerung
Einleitung
Der erste Artikel Grafisches Interface I: Vorbereiten der Bibliotheksstruktur (Kapitel 1) Beschreibt im Detail wofür diese Bibliothek gedacht ist. Am Ende von jedem Kapitel, finden Sie eine vollständige Liste mit Links zu diesem Artikel. Zudem finden Sie dort eine Möglichkeit das Projekt, entsprechend dem aktuellen Entwicklungsstand, herunterzuladen. Die Dateien müssen in den gleichen Verzeichnissen untergebracht werden, so, wie Sie auch in dem Archiv abgelegt sind.
Dieser Artikel handelt über drei Klassen, die es Ihnen erlauben, unterschiedliche Tabellentypen für zweidimensionale Datensätze als Teil einer MQL Anwendung zu programmieren:
- Text-Label Tabelle;
- Edit-Box Tabelle;
- Gerenderte Tabelle.
Jeder Tabellentyp hat seine eigenen besonderen Eigenschaften und Vorteile, die wir nachfolgend beschreiben.
In den nachfolgenden Artikel (Teil VII, Kapitel 2), werden wir die folgenden controls besprechen:
- Tabs;
- Tabs mit Bildern.
Das Text-Label Tabellen-Control
Eine Tabelle ist ein komplexes GUI control, da es auch andere Controls beinhaltet - horizontale und vertikale Scrollbars. Da es vorkommen kann, dass die Datenmenge den verfügbaren sichtbaren Platz innerhalb eines Controls überschreitet, bieten Ihnen Scrollbars die Möglichkeit, die Daten in horizontaler und vertikaler Richtung hin und her zu schieben.
Text-Label Tabellen bestehend aus den folgenden Komponenten:
- Hintergrund
- Text-Labels.
- Vertikale Scrollbar.
- Horizontale Scrollbar.
Abbildung 1. Komponenten des Text-Label Tabellen-Controls
Lassen Sie uns die Klasse dieses Controls näher anschauen.
Entwicklung der CLabelsTable Klasse
Erzeugen Sie die LabelsTable.mqh Datei und beziehen Sie diese in der Bibliothek (WndContainer.mqh) mit ein:
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include "LabelsTable.mqh"
Erzeugen sie in der LabelsTable.mqh Datei die Klasse CLabelsTable mit dem Standardsatz der Methoden, die in jedem Control dieser Bibliothek vorhanden sein sollten, sowie die Methode für das Abspeichern des Pointers des Formulars, zu welchem das Control hinzugefügt wird. Machen Sie dieses mit allen Controls, die wir in diesem Artikel besprechen.
//+------------------------------------------------------------------+ //| Class for creating a text label table | //+------------------------------------------------------------------+ class CLabelsTable : public CElement { private: //--- Ein Pointer zu der Form zu welchem das Element hinzugefügt worden ist CWindow *m_wnd; //--- public: CLabelsTable(void); ~CLabelsTable(void); //--- (1) Speichert den Pointer das Formulars, (2) Gibt den Pointer zu den Scrollbars zurück void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } //--- public: //--- Chart Eventhandler virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- Timer virtual void OnEventTimer(void); //--- Bewegen des Elementes virtual void Moving(const int x,const int y); //--- (1) Anzeigen, (2) verstecken, (3) zurücksetzen, (4) löschen virtual void Show(void); virtual void Hide(void); virtual void Reset(void); virtual void Delete(void); //--- (1) Setzen, (2) Zurücksetzen der Prioritäten der linken Maustaste virtual void SetZorders(void); virtual void ResetZorders(void); //--- Zurücksetzen der Farbe virtual void ResetColors(void) {} }; //+------------------------------------------------------------------+ //| Konstruktor | //+------------------------------------------------------------------+ CLabelsTable::CLabelsTable(void) { } //+------------------------------------------------------------------+ //| Destruktor | //+------------------------------------------------------------------+ CLabelsTable::~CLabelsTable(void) { }
Bevor wir die Tabelle erzeugen, spezifizieren wir einige ihre Eigenschaften. Lassen Sie uns diese benennen
- Die Höhe der Zeile
- Der erste Spalten-Einzug von der linken Kante des Steuerelementes
- Der Abstand zwischen den Spalten
- Die Hintergrundfarbe
- Die Textfarbe
- Der Sperrmodus der ersten Reihe
- Der Sperrmodus der ersten Spalte
- Gesamtanzahl der Spalten
- Gesamtanzahl der Reihen
- Die Anzahl der sichtbaren Spalten
- Die Anzahl der sichtbaren Zeilen
Bei diesem Tabellentyp, werden die Koordinaten des Text-Labels von dem zentralen Ankerpunkt aus berechnet (ANCHOR_CENTER). Daher wird der Einzug für die erste Spalte der Text-Labels von der linken Ecke des Controls aus berechnet und der Abstand zwischen den Text Labels jeder Spalte wird von dem Mittelpunkt jeder Spalte berechnet.
In den Sperrmodi muss es möglich sein, dass die erste Reihe und/oder Zeile (in welchen der User die Namen für jede Spalte und Zeile angibt) sichtbar bleibt, auch wenn der Anwender die Schieberegler der vertikalen und horizontalen Scrollbars verschiebt. Die Sperrmodi können zusammen oder separat genutzt werden.
class CLabelsTable : public CElement { private: //--- Die Höhe der Zeile int m_row_y_size; //--- Die Hintergrundfarbe der Tabelle color m_area_color; //--- Die Standardfarbe des Textes der Tabelle color m_text_color; //--- Der Abstand zwischen dem Ankerpunkt der ersten Spalte und der linken Ecke des Controls int m_x_offset; //--- Der Abstand zwischen den Ankerpunkten der Spalten int m_column_x_offset; //--- Sperrmodus der ersten Zeile bool m_fix_first_row; //--- Sperrmodus der ersten Spalte bool m_fix_first_column; //--- Priorität für die linke Maustaste int m_zorder; int m_area_zorder; //--- public: //--- (1) Hintergrundfarbe, (2) Textfarbe void AreaColor(const color clr) { m_area_color=clr; } void TextColor(const color clr) { m_text_color=clr; } //--- (1) Höhe der Zeile, (2) Festlegen des Abstandes zwischen dem Ankerpunkt der ersten Spalte und der linken Ecke der Tabelle, // (3) festlegen der Abstände zwischen den Ankerpunkten der Spalten void RowYSize(const int y_size) { m_row_y_size=y_size; } void XOffset(const int x_offset) { m_x_offset=x_offset; } void ColumnXOffset(const int x_offset) { m_column_x_offset=x_offset; } //--- (1) Abfrage und (2) des Sperrmodus für die erste Zeile bool FixFirstRow(void) const { return(m_fix_first_row); } void FixFirstRow(const bool flag) { m_fix_first_row=flag; } //--- (1) Abfrage und (2) des Sperrmodus für die erste Spalte bool FixFirstColumn(void) const { return(m_fix_first_column); } void FixFirstColumn(const bool flag) { m_fix_first_column=flag; } };
Lassen Sie uns die CLabelsTable::TableSize() und CLabelsTable::VisibleTableSize() Methoden, um die totale sichtbare Anzahl von Spalten und Zeilen festlegen zu können, erzeugen. Zudem benötigen wir noch zweidimensionale dynamische Arrays in Form von Strukturen. Eine dieser Strukturen (LTLabels) erzeugt das Array mit Text-Labeln für den sichtbaren Bereich der Tabelle, während das Andere (LTOptions) alle Werte und Eigenschaften jeder Zelle der Tabelle speichert. In unserer Implementation, speichert dieses die dargestellten Werte (Zeilen) und Textfarben
Den CLabelsTable::TableSize() und CLabelsTable::VisibleTableSize() Methoden werden zwei Argumente (Anzahl der Spalten und Zeilen) übergeben. Am Anfang der Methode werden die Werte daraufhin überprüft, ob die Anzahl der Spalten kleiner als eins ist und ob die Anzahl der Zeilen kleiner als zwei ist. Anschließend werden die größen aller Arraysd definiert und die Eigenschaften der Arrays werden initialisiert.
Neben den Methoden für die Größe der Tabelle, benötigen wir auch Methoden für die Abfrage der gesamten sichtbaren Größe mit Spalten und Zeilen.
class CLabelsTable : public CElement { private: //--- Array mit Objekten für den sichtbaren Bereich der Tabelle struct LTLabels { CLabel m_rows[]; }; LTLabels m_columns[]; //--- Array mit den Werten und Eigenschaften der Tabelle struct LTOptions { string m_vrows[]; color m_colors[]; }; LTOptions m_vcolumns[]; //--- public: //--- Gib die gesamte Anzahl von (1) Zeilen und (2) Spalten zurück int RowsTotal(void) const { return(m_rows_total); } int ColumnsTotal(void) const { return(m_columns_total); } //--- Gibt die Anzahl von (1) Zeilen und (2) Spalten des sichtbaren Bereichs der Tabelle zurück int VisibleRowsTotal(void) const { return(m_visible_rows_total); } int VisibleColumnsTotal(void) const { return(m_visible_columns_total); } //--- Setzen der (1) Größe der Tabelle und (2) Der Größe des sichtbaren Bereiches void TableSize(const int columns_total,const int rows_total); void VisibleTableSize(const int visible_columns_total,const int visible_rows_total); }; //+------------------------------------------------------------------+ //| Legt die Größe der Tabelle fest | //+------------------------------------------------------------------+ void CLabelsTable::TableSize(const int columns_total,const int rows_total) { //--- Es muss mindestens eine Spalte geben m_columns_total=(columns_total<1) ? 1 : columns_total; //--- Es muss mindestens zwei Zeilen geben m_rows_total=(rows_total<2) ? 2 : rows_total; //--- Festlegen der Größe der Spalten-Arrays ::ArrayResize(m_vcolumns,m_columns_total); //--- Festlegen der Größe der Zeilen-Arrays for(int i=0; i<m_columns_total; i++) { ::ArrayResize(m_vcolumns[i].m_vrows,m_rows_total); ::ArrayResize(m_vcolumns[i].m_colors,m_rows_total); //--- Initialisieren des Arrays der Textfarben mit den Standardwerten ::ArrayInitialize(m_vcolumns[i].m_colors,m_text_color); } } //+-----------------------------------------------------------------+ //| Festlegen der Größe des sichtbaren Teils der Tabelle | //+-----------------------------------------------------------------+ void CLabelsTable::VisibleTableSize(const int visible_columns_total,const int visible_rows_total) { //--- Es muss mindestens eine Spalte geben m_visible_columns_total=(visible_columns_total<1) ? 1 : visible_columns_total; //--- Es muss mindestens zwei Zeilen geben m_visible_rows_total=(visible_rows_total<2) ? 2 : visible_rows_total; //--- Festlegen der Größe der Spalten-Arrays ::ArrayResize(m_columns,m_visible_columns_total); //--- Festlegen der Größe der Zeilen-Arrays for(int i=0; i<m_visible_columns_total; i++) ::ArrayResize(m_columns[i].m_rows,m_visible_rows_total); }
Alle Eigenschaften sollten mit den Standardwerten initialisiert werden, bevor das Control erzeugt wird. Ich empfehle dieses direkt in den Konstruktor der Klasse vorzunehmen:
//+----------------------------------------------------------------+ //| Konstruktor | //+----------------------------------------------------------------+ CLabelsTable::CLabelsTable(void) : m_fix_first_row(false), m_fix_first_column(false), m_row_y_size(18), m_x_offset(30), m_column_x_offset(60), m_area_color(clrWhiteSmoke), m_text_color(clrBlack), m_rows_total(2), m_columns_total(1), m_visible_rows_total(2), m_visible_columns_total(1) { //--- Abspeichern des namens der Elementklasse in der Basisklasse CElement::ClassName(CLASS_NAME); //--- Setzen der Priorität eines Klicks mit der linken Maustaste m_zorder =0; m_area_zorder =1; //--- Festlegen der Größe der Tabelle und seines sichtbaren Bereiches TableSize(m_columns_total,m_rows_total); VisibleTableSize(m_visible_columns_total,m_visible_rows_total); }
Lassen Sie uns vier private und eine public Method für einen externen Aufruf für die Entwicklung des Controls erzeugen. Um es den Anwender ermöglichen zu können, die Scrollbars der Tabelle konfigurieren zu können, müssen wir eine Methode hinzufügen, die deren Pointer zurückgibt.
class CLabelsTable : public CElement { private: //--- Objekte für die Erzeugung einer Tabelle CRectLabel m_area; CScrollV m_scrollv; CScrollH m_scrollh; //--- Array mit Objekten für den sichtbaren Bereich der Tabelle struct LTLabels { CLabel m_rows[]; }; LTLabels m_columns[]; //--- public: //--- Gibt die Pointer der Scrollbars zurück CScrollV *GetScrollVPointer(void) const { return(::GetPointer(m_scrollv)); } CScrollH *GetScrollHPointer(void) const { return(::GetPointer(m_scrollh)); } //--- Methoden für die Erzeugung der Tabelle bool CreateLabelsTable(const long chart_id,const int subwin,const int x,const int y); //--- private: bool CreateArea(void); bool CreateLabels(void); bool CreateScrollV(void); bool CreateScrollH(void); };
Von all den Methoden für die Erzeugung, wird hier nur die CLabelsTable::CreateLabels() Für die Entwicklung des Text-Label Arrays gezeigt. Stellen Sie sicher, dass Sie die Spalten und Zeilen Indizes hinzufügen wenn Sie den Objektnamen bilden. Alle anderen Methoden finden Sie in den beigefügten Dateien.
//+-----------------------------------------------------------------+ //| Erzeugung eines Arrays mit Text-Labeln | //+-----------------------------------------------------------------+ bool CLabelsTable::CreateLabels(void) { //--- Koordinaten und Offset int x =CElement::X(); int y =0; int offset =0; //--- Spalten for(int c=0; c<m_visible_columns_total; c++) { //--- Berechnung des Tabellen-Offsets offset=(c>0) ? m_column_x_offset : m_x_offset; //--- Berechnung der x-Koordinate x=x+offset; //--- Zeilen for(int r=0; r<m_visible_rows_total; r++) { //--- Bilden des Objektnamens string name=CElement::ProgramName()+"_labelstable_label_"+(string)c+"_"+(string)r+"__"+(string)CElement::Id(); //--- Berechne die Y Koordinate y=(r>0) ? y+m_row_y_size-1 : CElement::Y()+10; //--- Erzeugen des Objektes if(!m_columns[c].m_rows[r].Create(m_chart_id,name,m_subwin,x,y)) return(false); //--- Festlegen der Eigenschaften m_columns[c].m_rows[r].Description(m_vcolumns[c].m_vrows[r]); m_columns[c].m_rows[r].Font(FONT); m_columns[c].m_rows[r].FontSize(FONT_SIZE); m_columns[c].m_rows[r].Color(m_text_color); m_columns[c].m_rows[r].Corner(m_corner); m_columns[c].m_rows[r].Anchor(ANCHOR_CENTER); m_columns[c].m_rows[r].Selectable(false); m_columns[c].m_rows[r].Z_Order(m_zorder); m_columns[c].m_rows[r].Tooltip("\n"); //--- Abstände von der Ecke des Formulars m_columns[c].m_rows[r].XGap(x-m_wnd.X()); m_columns[c].m_rows[r].YGap(y-m_wnd.Y()); //--- Koordinaten m_columns[c].m_rows[r].X(x); m_columns[c].m_rows[r].Y(y); //--- Abspeichern des Objekt-Pointers CElement::AddToArray(m_columns[c].m_rows[r]); } } //--- return(true); }
Wir benötigen noch Methoden, um die Werte und Eigenschaften jeder Zelle der Tabelle zu jeder Zeit ändern zu können. Lassen Sie uns die CLabelsTable::SetValue() und CLabelsTable::GetValue() Methoden für das setzen und Abfragen des Wertes einer Zelle erzeugen. Bei beiden Methoden müssen die Indizes der Spalte und Zeile als erste Argumente übergeben werden. Der Wert, der anschließend in dem Array abgespeichert werden soll, wird als drittes Argument der CLabelsTable::SetValue() Methode übergeben. Zunächst folgt natürlich die obligatorische Überprüfung, ob die Indexwerte die Grenzen des Arrays überschreiten.
class CLabelsTable : public CElement { public: //--- Festlegen eines Wertes in der spezifizierten Zelle einer Tabelle void SetValue(const int column_index,const int row_index,const string value); //--- Abfrage des Wertes einer bestimmten Zelle einer Tabelle string GetValue(const int column_index,const int row_index); }; //+-----------------------------------------------------------------+ //| Setzen des Wertes bei den angegebenen Indizes | //+-----------------------------------------------------------------+ void CLabelsTable::SetValue(const int column_index,const int row_index,const string value) { //--- Überprüfung auf Überschreitung des Indexbereiches der Spalten int csize=::ArraySize(m_vcolumns); if(csize<1 || column_index<0 || column_index>=csize) return; //--- Überprüfung der Überschreitung des Indexbereiches der Zeilen int rsize=::ArraySize(m_vcolumns[column_index].m_vrows); if(rsize<1 || row_index<0 || row_index>=rsize) return; //--- Setzen des Wertes m_vcolumns[column_index].m_vrows[row_index]=value; } //+-----------------------------------------------------------------+ //| Rückgabe des Wertes an den angegebenen Indizes | //+-----------------------------------------------------------------+ string CLabelsTable::GetValue(const int column_index,const int row_index) { //--- Überprüfung auf Überschreitung des Indexbereiches der Spalten int csize=::ArraySize(m_vcolumns); if(csize<1 || column_index<0 || column_index>=csize) return(""); //--- Überprüfung der Überschreitung des Indexbereiches der Zeilen int rsize=::ArraySize(m_vcolumns[column_index].m_vrows); if(rsize<1 || row_index<0 || row_index>=rsize) return(""); //--- Rückgabe des Wertes return(m_vcolumns[column_index].m_vrows[row_index]); }
Neben dem Ändern eines Wertes in der Tabelle, möchten die Anwender der Bibliothek vielleicht auch noch die Farbe des Textes ändern. Zum Beispiel könnten positive Werte in Grün dargestellt werden, und negative Werte in Rot. Lassen Sie uns dafür die CLabelsTable::TextColor() Methode erzeugen. Sie ist der CLabelsTable::SetValue() Methode sehr ähnlich, mit dem einzigen Unterschied, dass als drittes Argument die Farbe angegeben wird.
class CLabelsTable : public CElement { public: //--- Die Textfarbe einer bestimmten Zelle ändern void TextColor(const int column_index,const int row_index,const color clr); }; //+-----------------------------------------------------------------+ //| Ändert die Farbe einer bestimmten Zelle | //+-----------------------------------------------------------------+ void CLabelsTable::TextColor(const int column_index,const int row_index,const color clr) { //--- Überprüfung auf Überschreitung des Indexbereiches der Spalten int csize=::ArraySize(m_vcolumns); if(csize<1 || column_index<0 || column_index>=csize) return; //--- Überprüfung der Überschreitung des Indexbereiches der Zeilen int rsize=::ArraySize(m_vcolumns[column_index].m_vrows); if(rsize<1 || row_index<0 || row_index>=rsize) return; //--- Festlegen der Farbe m_vcolumns[column_index].m_colors[row_index]=clr; }
Die Änderungen werden erst nach einem Update der Tabelle übernommen / angezeigt. Dafür erzeugen wir eine universelle Methode, welche auch benutzt wird, wenn die Daten entsprechend der Position der Schieberegler der Scrollbars verschoben werden müssen.
Lassen Sie uns diese Methode CLabelsTable::UpdateTable() nennen. Bin die Kopfzeilen gesperrt sind, dann beginnt die Verschiebung mit dem zweiten (1) Array-Index , damit sichergestellt ist, dass die erste Zeile, beziehungsweise die erste Spalte immer sichtbar bleibt. Die t und l Variabeln werden am Anfang der Methode deklariert und deren Werte von 0 oder 1 werden entsprechend dem aktuell verwendeten Modus gesetzt.
Um den Index definieren zu können, von welchem aus eine Verschiebung, beziehungsweise ein Update gestartet werden muss, muss die aktuelle Position der Schieberegler der Scrollbars abgefragt werden. Die Kopfzeilen der linken Spalte und der obersten Zeile werden in separaten schleifen bewegt (falls dieser Modus aktiviert ist).
Die Hauptdaten der Tabelle und die Farben der Zellen, werden in der doppelten Schleife am Ende der Methode bewegt.
class CLabelsTable : public CElement { public: //--- Aktualisierung der Daten der Tabelle unter Berücksichtigung der letzten Veränderungen void UpdateTable(void); }; //+-----------------------------------------------------------------------------------------+ //| Aktualisierung der Daten der Tabelle unter Berücksichtigung der letzten Veränderungen | //+-----------------------------------------------------------------------------------------+ void CLabelsTable::UpdateTable(void) { //--- Verschieben um einen Index, falls der fixierte Kopfzeilen-Modus aktiviert ist int t=(m_fix_first_row) ? 1 : 0; int l=(m_fix_first_column) ? 1 : 0; //--- Abfrage der aktuellen Positionen der Schieberegler der vertikalen und horizontalen Scrollbars int h=m_scrollh.CurrentPos()+l; int v=m_scrollv.CurrentPos()+t; //--- Verschieben der Kopfzeile in der linken Spalte if(m_fix_first_column) { m_columns[0].m_rows[0].Description(m_vcolumns[0].m_vrows[0]); //--- Zeilen for(int r=t; r<m_visible_rows_total; r++) { if(r>=t && r<m_rows_total) m_columns[0].m_rows[r].Description(m_vcolumns[0].m_vrows[v]); //--- v++; } } //--- Verschieben der Kopfzeile in der obersten Zeile if(m_fix_first_row) { m_columns[0].m_rows[0].Description(m_vcolumns[0].m_vrows[0]); //--- Spalten for(int c=l; c<m_visible_columns_total; c++) { if(h>=l && h<m_columns_total) m_columns[c].m_rows[0].Description(m_vcolumns[h].m_vrows[0]); //--- h++; } } //--- Abfrage der aktuellen Position des Schiebereglers der Horizontalen Scrollbar h=m_scrollh.CurrentPos()+l; //--- Spalten for(int c=l; c<m_visible_columns_total; c++) { //--- Abfrage der aktuellen Position des Schiebereglers der vertikalen Scrollbar v=m_scrollv.CurrentPos()+t; //--- Zeilen for(int r=t; r<m_visible_rows_total; r++) { //--- Verschiebung der Tabellendaten if(v>=t && v<m_rows_total && h>=l && h<m_columns_total) { //--- Einstellung der Farbe m_columns[c].m_rows[r].Color(m_vcolumns[h].m_colors[v]); //--- Einstellen der Werte m_columns[c].m_rows[r].Description(m_vcolumns[h].m_vrows[v]); v++; } } //--- h++; } }
Lassen Sie uns, ähnlich wie wir es auch schon bei der Inputbox und den List-Controls gemacht haben, das schnelle zurückscrollen implementieren, sobald die linke Maustaste über oberhalb der Scrollbar Buttons gedrückt wird. Es gibt keinen Grund, um den Programmcode der CLabelsTable::FastSwitching() Methode hier darzustellen, da er dem Programmcode der Methoden aus den vorherigen Artikeln (CListView, CSpinEdit and CCheckBoxEdit Klassen) entspricht.
Der Programmcode der Methoden für das Verarbeiten der Control-Events wird in dem nachfolgenden Listing gezeigt:
//+-----------------------------------------------------------------+ //| Even handler | //+-----------------------------------------------------------------+ void CLabelsTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Verarbeiten der Events über die Bewegung des Mauszeigers if(id==CHARTEVENT_MOUSE_MOVE) { //--- Abbrechen, falls das Element versteckt ist if(!CElement::IsVisible()) return; //---Koordinaten und der Status der linken Maustaste int x=(int)lparam; int y=(int)dparam; m_mouse_state=(bool)int(sparam); //--- Überprüfen des Fokus über der Tabelle CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); //--- Bewege die Liste, falls die Verwaltung des Schiebereglers aktiviert ist if(m_scrollv.ScrollBarControl(x,y,m_mouse_state) || m_scrollh.ScrollBarControl(x,y,m_mouse_state)) UpdateTable(); //--- return; } //--- Verarbeitung von Klicks auf Objekte if(id==CHARTEVENT_OBJECT_CLICK) { //--- Falls auf einen Button der Tabellen-Scrollbars gedrückt wurde if(m_scrollv.OnClickScrollInc(sparam) || m_scrollv.OnClickScrollDec(sparam) || m_scrollh.OnClickScrollInc(sparam) || m_scrollh.OnClickScrollDec(sparam)) //--- Verschiebe die Tabelle relativ zu der Scrollbar UpdateTable(); //--- return; } } //+-----------------------------------------------------------------+ //| Timer | //+-----------------------------------------------------------------+ void CLabelsTable::OnEventTimer(void) { //--- Falls es sich um einen DropDown-Element handelt if(CElement::IsDropdown()) FastSwitching(); //--- Falls es sich nicht um ein Dropdown-Element handelt, berücksichtige die aktuelle Verfügbarkeit des Formulars else { //--- Verfolge den schnellen Vorlauf der Tabelle nur, falls das Formular nicht gesperrt ist if(!m_wnd.IsLocked()) FastSwitching(); } }
Eine Tabelle ist ein komplexes GUI-Control. Daher sollten die Pointer der anderen Controls (die horizontale und vertikale Scrollbar) mit in der Pointer-Datenbank mit einbezogen werden. Wir sollten ein eigenständiges Array für Pointer von Tabellen erstellen. Sie finden alle diese Veränderungen in der WndContainer.mqh Datei der CWndContainer Klasse. Dieses Thema wurde bereits in anderen Artikeln dieser Serie abgedeckt. Daher überspringe ich dieses hier und fahre mit dem Test der Text-Label-Tabelle fort.
Test der Text Label Tabelle
Zu Testzwecken werden wir den Expert Advisor aus dem vorherigen Artikel verwenden und nur das Hauptmenü und die Statusleiste behalten. Um die Text-Label-Tabelle einer MQL Anwendung hinzufügen zu können, muss die CLabelsTable Typenklasseninstanz, Sowie die Methoden und Abstände von der äußersten Spitze des Formulars deklariert werden (Diese sehen Sie im nachfolgendem Programmcode).
class CProgram : public CWndEvents { private: //--- Text-Label-Tabelle CLabelsTable m_labels_table; //--- private: //--- Text-Label-Tabelle #define LABELS_TABLE1_GAP_X (1) #define LABELS_TABLE1_GAP_Y (42) bool CreateLabelsTable(void); };
Lassen Sie uns nun den Programmcode der CProgram::CreateLabelsTable() Method genauer betrachten. Wir möchten eine Tabelle, die aus 21 Spalten und 100 Zeilen besteht entwerfen. Die Anzahl der sichtbaren Spalten ist 5, und die Anzahl der sichtbaren Zeilen ist 10. Wir fixieren die oberste Zeile und die linke Spalte, damit sie nicht bewegt werden. Nachdem die Tabelle erstellt worden ist, wird sie mit zufälligen Werten (von -1000 bis 1000) gefüllt und es werden noch die Farben definiert. Positive Werte werden in Grün dargestellt und negative Werte in Rot. Stellen Sie sicher, dass Sie die Tabelle aktualisieren, damit die letzten Änderungen sichtbar werden.
//+-----------------------------------------------------------------+ //| Erzeugung der Text Label Tabelle | //+-----------------------------------------------------------------+ bool CProgram::CreateLabelsTable(void) { #define COLUMNS1_TOTAL (21) #define ROWS1_TOTAL (100) //--- Abspeichern des pointers zu dem Formular m_labels_table.WindowPointer(m_window1); //--- Koordinaten int x=m_window1.X()+LABELS_TABLE1_GAP_X; int y=m_window1.Y()+LABELS_TABLE1_GAP_Y; //--- Die Anzahl der sichtbaren Spalten und Zeilen int visible_columns_total =5; int visible_rows_total =10; //--- Setzen der Eigenschaften m_labels_table.XSize(400); m_labels_table.XOffset(40); m_labels_table.ColumnXOffset(75); m_labels_table.FixFirstRow(true); m_labels_table.FixFirstColumn(true); m_labels_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL); m_labels_table.VisibleTableSize(visible_columns_total,visible_rows_total); //--- Erzeugen der Tabelle if(!m_labels_table.CreateLabelsTable(m_chart_id,m_subwin,x,y)) return(false); //--- Einfügen in die Tabelle: // Die erste Zelle ist leer m_labels_table.SetValue(0,0,"-"); //--- Die Überschriften der Spalten for(int c=1; c<COLUMNS1_TOTAL; c++) { for(int r=0; r<1; r++) m_labels_table.SetValue(c,r,"SYMBOL "+string(c)); } //--- Die Überschriften der Zeilen, die Ausrichtung ist Rechts for(int c=0; c<1; c++) { for(int r=1; r<ROWS1_TOTAL; r++) m_labels_table.SetValue(c,r,"PARAMETER "+string(r)); } //--- Daten und Tabellen-Formatierung (Hintergrund und Farben der Zellen) for(int c=1; c<COLUMNS1_TOTAL; c++) { for(int r=1; r<ROWS1_TOTAL; r++) m_labels_table.SetValue(c,r,string(::rand()%1000-::rand()%1000)); } //--- Die Textfarbe in den Zellen setzen for(int c=1; c<m_labels_table.ColumnsTotal(); c++) for(int r=1; r<m_labels_table.RowsTotal(); r++) m_labels_table.TextColor(c,r,((double)m_labels_table.GetValue(c,r)>=0) ? clrGreen : clrRed); //--- Aktualisieren der Tabelle m_labels_table.UpdateTable(); //--- Hinzufügen des Pointers des Elementes zu der Basis CWndContainer::AddToElementsArray(0,m_labels_table); return(true); }
Zudem sollten wir testen, wie die Tabellenwerte geändert werden, wenn das Programm auf einem Terminal-Chart ausgeführt wird. Um dieses zu tun, wird der folgende Programmcode der CProgram::OnTimerEvent() Timer-Methode der Anwendung hinzugefügt. Schauen Sie sich dazu das nachfolgende Listing an:
//+----------------------------------------------------------------+ //| Timer | //+----------------------------------------------------------------+ void CProgram::OnTimerEvent(void) { CWndEvents::OnTimerEvent(); //--- Aktualisierung des zweiten Punktes der Statuszeile alle 500 Millisekunden static int count=0; if(count<500) { count+=TIMER_STEP_MSC; return; } //--- Zurücksetzen des Timers count=0; //--- Veränderung des Wertes in dem zweiten Punkt der Statuszeile. m_status_bar.ValueToItem(1,::TimeToString(::TimeLocal(),TIME_DATE|TIME_SECONDS)); //--- Tabelle mit Daten auffüllen for(int c=1; c<m_labels_table.ColumnsTotal(); c++) for(int r=1; r<m_labels_table.RowsTotal(); r++) m_labels_table.SetValue(c,r,string(::rand()%1000-::rand()%1000)); //--- Die Textfarbe in den Zellen setzen for(int c=1; c<m_labels_table.ColumnsTotal(); c++) for(int r=1; r<m_labels_table.RowsTotal(); r++) m_labels_table.TextColor(c,r,((double)m_labels_table.GetValue(c,r)>=0) ? clrGreen : clrRed); //--- Aktualisieren der Tabelle m_labels_table.UpdateTable(); }
Die Methode für das Erzeugen der Text Label Tabelle sollte in der Hauptmethode CProgram::CreateExpertPanel() der Anwendung aufgerufen werden. Die Kurzfassung des Verfahrens ist unten dargestellt:
//+----------------------------------------------------------------+ //| Erzeugung des Expert-Bedienfeldes | //+----------------------------------------------------------------+ bool CProgram::CreateExpertPanel(void) { //--- Erzeugen des Formulars 1 für die Controls //--- Erzeugen der Controls: // Hauptmenü //--- Kontextmenüs //--- Erzeugen der Statuszeile //--- Text-Label-Tabelle if(!CreateLabelsTable()) return(false); //--- Neuzeichnen des Charts m_chart.Redraw(); return(true); }
Kompilieren Sie den Programmcode und laden Sie das Programm auf einen Chart. Der nachfolgende Screenshot zeigt das Resultat:
Abbildung 2. Test der Text-Label-Tabelle
Alles arbeitet einwandfrei. Lassen Sie uns nun mit der Klasse für die Erzeugung des zweiten Typs der Tabellen fortfahren.
Das Edit-Box Tabellen-Control
Anders als die Text-Label-Tabelle, bietet die Editbox-Tabelle eine größere Flexibilität und mehr Eigenschaften. Außer der Möglichkeit, die Textfarbe zu verändern, bietet sie noch:
- Die Ausrichtung des Textes innerhalb einer Zelle(links/mitte/rechts)
- Veränderung der Hintergrundfarbe und des Rahmens der Editbox;
- Die Möglichkeit, die Werte in der Edit-Box manuell zu verändern, falls der entsprechende Modus aktiviert ist.
Alle diese Punkte machen diese Tabelle noch benutzerfreundlicher und erleichtern die Verwendung bei einer großen Menge von Aufgaben. Text-Label Tabellen bestehend aus den folgenden Komponenten:
- Hintergrund
- Edit boxes
- Vertikale Scrollbar.
- Horizontale Scrollbar.
Abbildung 3. Die Komponenten des Edit-Box Tabellen-Controls
Lassen Sie uns ansehen, wie sich der Programmcode zu der vorherigen Tabelle unterscheidet.
Entwicklung der CTable Klasse
Lassen Sie uns die Eigenschaften der Tabelle beschreiben und dabei die Unterschiede zu der vorherigen Tabelle hervorheben. Das Array für die sichtbaren grafischen Objekte, ist bei dieser Tabelle voneinem anderen Typ– (CEdit). Mit anderen Worten, es besteht aus Edit-Boxen anstelle von Text-Labels (sehen Sie sich dazu den nachfolgenden Programmcode an).
class CTable : public CElement { private: //--- Array mit Objekten für den sichtbaren Bereich der Tabelle struct TEdits { CEdit m_rows[]; }; TEdits m_columns[]; };
Es gibt hier mehr einzigartige Eigenschaften für jede Zelle, da die Tabelle es Ihnen erlaubt, die Ausrichtung des Textes und die Hintergrundfarbe der Editbox zu verändern.
class CTable : public CElement { private: //--- Arrays mit den Tabellenwerten und Eigenschaften struct TOptions { string m_vrows[]; ENUM_ALIGN_MODE m_text_align[]; color m_text_color[]; color m_cell_color[]; }; TOptions m_vcolumns[]; };
Nachfolgend ist eine Auflistung der Modi und Eigenschaften, welche in der Text-Label-Tabelle nicht vorhanden sind.
- Der "Editierbar"-Tabellenmodus
- Der Modus zum Hervorheben einer Zeile, sobald sich der Mauszeiger darüber befindet.
- Der Modus für eine ausgewählte Zeile
- Die Höhe der Zeile
- Farbe des Tabellen-Gitters
- Hintergrundfarbe der Überschriften
- Textfarbe der Überschriften
- Die Farbe der Zelle, sobald sich der Mauszeiger darüber befindet.
- Die Standardfarbe der Zelle
- Die Standard-Ausrichtung einer Zelle
- Die hervorgehobene Hintergrundfarbe einer Zeile
- Die Textfarbe im Falle des Hervorhebens
Dieser Tabellentyp, erlaubt es Ihnen ebenfalls, die Überschriften der ersten Zeile und der ersten Spalte zu fixieren. Wenn dieser Modus gewählt ist, dann bleiben diese an ihrem Platz wenn die Schieberegler bewegt werden. Der nachfolgende Programmcode zeigt die vollständigen Edit-Boxen und Methoden für das Setzen der Tabelleneigenschaften:
class CTable : public CElement { private: //--- Höhe der Zeilen der Tabelle int m_row_y_size; //--- (1) Die Farbe des Hintergrundes und (2) Des Rahmens des Hintergrundes der Tabelle color m_area_color; color m_area_border_color; //--- Farbe des Gitters color m_grid_color; //--- Hintergrundfarbe der Überschrift color m_headers_color; //--- Textfarbe der Überschrift color m_headers_text_color; //--- Die Farben der Zellen in den unterschiedlichen Zuständen color m_cell_color; color m_cell_color_hover; //--- Die Standardfarbe für den Text einer Zelle color m_cell_text_color; //--- Farbe des (1) Hintergrundes und (2) des Textes der selektierten Zeile color m_selected_row_color; color m_selected_row_text_color; //--- Modus für die Editierbarkeit der Tabelle bool m_read_only; //--- Modus für das Hervorheben von Zeilen, wenn sich der Mauszeiger darüber befindet bool m_lights_hover; //--- Modus für die Auswählbarkeit einer Zeile bool m_selectable_row; //--- Sperrmodus der ersten Zeile bool m_fix_first_row; //--- Sperrmodus der ersten Spalte bool m_fix_first_column; //--- Die Standard-Textausrichtung in den Edit-Boxen ENUM_ALIGN_MODE m_align_mode; //--- public: //--- Farbe des (1) Hintergrundes und (2) des Rahmens der Tabelle void AreaColor(const color clr) { m_area_color=clr; } void BorderColor(const color clr) { m_area_border_color=clr; } //--- (1) Abfrage und (2) des Sperrmodus für die erste Zeile bool FixFirstRow(void) const { return(m_fix_first_row); } void FixFirstRow(const bool flag) { m_fix_first_row=flag; } //--- (1) Abfrage und (2) des Sperrmodus für die erste Spalte bool FixFirstColumn(void) const { return(m_fix_first_column); } void FixFirstColumn(const bool flag) { m_fix_first_column=flag; } //--- Farbe des (1) Hintergrundes der Überschriften, (2) Text der Überschriften und (3) Dis Tabellengitters void HeadersColor(const color clr) { m_headers_color=clr; } void HeadersTextColor(const color clr) { m_headers_text_color=clr; } void GridColor(const color clr) { m_grid_color=clr; } //--- Die Größe der Zeilen entlang der y-Achse void RowYSize(const int y_size) { m_row_y_size=y_size; } void CellColor(const color clr) { m_cell_color=clr; } void CellColorHover(const color clr) { m_cell_color_hover=clr; } //--- (1) "Nur lesen", (2) Hervorheben der Zeilen, wenn sich die Maus darüber befindet, (3) Modus für die Auswertbarkeit der Zeile void ReadOnly(const bool flag) { m_read_only=flag; } void LightsHover(const bool flag) { m_lights_hover=flag; } void SelectableRow(const bool flag) { m_selectable_row=flag; } //--- Ausrichtung des Textes in der Zelle void TextAlign(const ENUM_ALIGN_MODE align_mode) { m_align_mode=align_mode; } };
Nachfolgend sind die Eigenschaften der Methoden aufgelistet, die dazu dienen, die Werte der Tabelle zu setzen oder abzufragen, in Abhängigkeit der Spalten- und Zeilen-Indizes.
- Die Gesamtgröße der Tabelle (Die gesamt Anzahl der Spalten und Zeilen)
- Die Größe des sichtbaren Bereiches der Tabelle (Die Anzahl der sichtbaren Spalten und Zeilen)
- Die Textausrichtung der Zelle (links/rechts/mitte)
- Die Textfarbe
- Die Hintergrundfarbe
- Festlegen/Verändern des Wertes
- Abfrage des Wertes
Es gibt hier keinen Grund, den Programmcode dieser Methode nochmal darzustellen, da wir diesen bereits in dem Abschnitt für die Text-Label-Tabelle besprochen haben. Nachdem Sie alle Werte gesetzt haben, müssen Sie sicherstellen, dass Sie die Tabelle über den Aufruf der CTable::UpdateTable() Methode aktualisieren, damit alle Veränderungen sichtbar werden.
class CTable : public CElement { public: //--- Setzen der (1) Größe der Tabelle und (2) Der Größe des sichtbaren Bereiches void TableSize(const int columns_total,const int rows_total); void VisibleTableSize(const int visible_columns_total,const int visible_rows_total); //--- Setzen (1) des Ausrichtungs-Modus, (2) Textfarbe, (3) Hintergrundfarbe der Zelle void TextAlign(const int column_index,const int row_index,const ENUM_ALIGN_MODE mode); void TextColor(const int column_index,const int row_index,const color clr); void CellColor(const int column_index,const int row_index,const color clr); //--- Festlegen eines Wertes in der spezifizierten Zelle einer Tabelle void SetValue(const int column_index,const int row_index,const string value); //--- Abfrage des Wertes einer bestimmten Zelle einer Tabelle string GetValue(const int column_index,const int row_index); //--- Aktualisierung der Daten der Tabelle unter Berücksichtigung der letzten Veränderungen void UpdateTable(void); };
Lassen Sie uns nun die Methoden für die Verwaltung der Tabelle besprechen. Es handelt sich hierbei ausschließlich um private Klassen-Methoden für die interne Verwendung. Deren Funktionsumfang umfasst:
- Verarbeitung eines Klicks auf eine Zeile der Tabelle.
- Verarbeiten der Eingabe eines Wertes in die Zelle einer Tabelle.
- Abfrage der ID aus einem Objektnamen
- Abfrage des Index einer Spalte aus einem Objektnamen
- Abfrage des Index einer Zeile aus einem Objektnamen
- Hervorheben der ausgewählten Zeile
- Verändern der Zeilenfarbe, wenn sich der Mauszeiger über dem Button befindet.
- Schnelles zurückscrollen der Tabelle.
Lassen Sie uns mit der CTable::OnClickTableRow() Methode für das Anklicken einer Zeile einer Tabelle beginnen. Am Anfang der Methode müssen einige Überprüfungen durchgeführt werden. In den folgenden Fällen verlässt das Programm die Methode:
- Es ist der Modus für die Editierbarkeit der Tabelle ausgewählt;
- Wenn einer der Scrollbars aktiv ist;
- Ein Event für einen Klick gehört nicht zu der Zelle dieser Tabelle. Dieses kann durch den Programmnamen und der Zugehörigkeit der Zelle zu der Tabelle aus dem Objektnamen bestimmt werden;
- Die Control ID nicht zutrifft. Um die ID aus dem Objektnamen zu erhalten, wird die CTable::IdFromObjectName() Methode verwendet. Dieses Verfahren haben wir bereits bei der Untersuchung von anderen Controls beschrieben.
Nun ist es an der Zeit alle Zellen zu durchsuchen und nach der geklickten Zelle zu schauen, unter Berücksichtigung der gesperrten Zeile für die Überschrift und der aktuellen Position des Schiebereglers der vertikalen Scrollbar. Falls die gedrückte Zelle gefunden worden ist, dann wird der Zeilen-Index und der Wert der Zelle in den variablen der Klasse abgespeichert.
Falls auf die Überschrift (die erste Zeile) geklickt wurde, dann verlässt das Programm die Methode. Andernfalls wird eine benutzerdefinierte Nachricht erzeugt. Sie enthält die (1) Chart ID, (2) Event ID (ON_CLICK_LIST_ITEM), (3) Control ID und (4) Den Index der angeklickten Zeile.
class CTable : public CElement { private: //--- Verarbeiten eines Klicks auf eine Tabellenzeile bool OnClickTableRow(const string clicked_object); //--- Den Bezeichner aus dem Objektnamen entnehmen int IdFromObjectName(const string object_name); }; //+-----------------------------------------------------------------+ //| Verarbeiten eines Klicks auf eine Tabellenzeile | //+-----------------------------------------------------------------+ bool CTable::OnClickTableRow(const string clicked_object) { //--- Abbrechen, falls der Modus für das Editieren einer Tabelle aktiv ist if(!m_read_only) return(false); //--- Abbrechen, falls die Scrollbar aktiv ist if(m_scrollv.ScrollState() || m_scrollh.ScrollState()) return(false); //--- Abbrechen, falls es sich nicht um einen Klick auf eine Zelle dieser Tabelle handelt if(::StringFind(clicked_object,CElement::ProgramName()+"_table_edit_",0)<0) return(false); //--- Den Bezeichner aus dem Objektnamen entnehmen int id=IdFromObjectName(clicked_object); //--- Abbrechen, falls der Bezeichner nicht übereinstimmt if(id!=CElement::Id()) return(false); //--- Suche nach den Zeilenindex int row_index=0; //--- Verschieben um einen Index, falls der fixierte Kopfzeilen-Modus aktiviert ist int t=(m_fix_first_row) ? 1 : 0; //--- Spalten for(int c=0; c<m_visible_columns_total; c++) { //--- Abfrage der aktuellen Position des Schiebereglers der vertikalen Scrollbar int v=m_scrollv.CurrentPos()+t; //--- Zeilen for(int r=t; r<m_visible_rows_total; r++) { //--- Falls nicht auf diese Zelle geklickt wurde if(m_columns[c].m_rows[r].Name()==clicked_object) { //--- Speichere den Index der Zeile m_selected_item=row_index=v; //--- Speichere die Linie der Zelle m_selected_item_text=m_columns[c].m_rows[r].Description(); break; } //--- Erhöhe den Zähler für die Zeilen if(v>=t && v<m_rows_total) v++; } } //--- Abbrechen, falls auf die Überschrift geklickt wurde if(m_fix_first_row && row_index<1) return(false); //--- Eine Nachricht darüber senden ::EventChartCustom(m_chart_id,ON_CLICK_LIST_ITEM,CElement::Id(),m_selected_item,""); return(true); }
Lassen Sie uns die CTable::OnEndEditCell() Methode für die Verarbeitung einer Eingabe eines Wertes in eine Zelle schreiben. Die Methode muss zunächst auch ein paar Überprüfungen vornehmen. In den folgenden Fällen wird die Methode verlassen:
- Der Modus für die Editierbarkeit ist nicht aktiviert;
- Der Programmname oder die Zugehörigkeit der Zelle mit der Tabelle stimmt nicht überein.
- Die Control ID stimmt nicht überein.
Wenn alle Überprüfungen erfolgreich waren, dann erhalten wir unter Verwendung der Hilfsmethoden CTable::ColumnIndexFromObjectName() und CTable::RowIndexFromObjectName() die Zellenspalten und Zeilen des sichtbaren Bereiches (Indices aus dem Array der grafischen Objekte). Nun müssen wir noch die aktuelle Position der Schieberegler der Scrollbars den Indizes der Objekte hinzufügen damit wir den richtigen Index des Data-arrays erhalten. Anschließend müssen wir noch den Zeilenindex korrigieren, falls der Modus für die fixierte Überschrift aktiviert ist und der Index des Arrays = 0 ist. Anschließen müssen wir überprüfen, ob sich der Wert der Zelle geändert hat. Falls ja, dann wird der neue Wert in dem entsprechenden Daten-Array gespeichert und eine Nachricht, die (1) die Chart ID, (2) die Event ID (ON_END_EDIT), (3) die Control ID und die (4) Zeile, die aus dem Index der Spalte und der Zeile und dem Wert der aktuellen Zelle generiert wird, enthält. Das "_" Symbol wird als Trennzeichen innerhalb der Zeile verwendet.
class CTable : public CElement { private: //--- Verarbeiten der manuellen Eingabe eines Wertes in eine Zelle bool OnEndEditCell(const string edited_object); //--- Abfrage des Spaltenindex aus dem Objektnamen int ColumnIndexFromObjectName(const string object_name); //--- Abfrage des Index einer Zeile aus einem Objektnamen int RowIndexFromObjectName(const string object_name); }; //+----------------------------------------------------------------------+ //| Event für das Abschließen einer Eingabe eines Wertes in eine Zelle | //+----------------------------------------------------------------------+ bool CTable::OnEndEditCell(const string edited_object) { //--- Abbrechen, falls der Modus für die Editierbarkeit deaktiviert ist if(m_read_only) return(false); //--- Abbrechen, falls es sich nicht um einen Klick auf eine Zelle dieser Tabelle handelt if(::StringFind(edited_object,CElement::ProgramName()+"_table_edit_",0)<0) return(false); //--- Den Bezeichner aus dem Objektnamen entnehmen int id=IdFromObjectName(edited_object); //--- Abbrechen, falls der Bezeichner nicht übereinstimmt if(id!=CElement::Id()) return(false); //--- Abfrage des Spalten- und Zeilen- Index der Zelle int c =ColumnIndexFromObjectName(edited_object); int r =RowIndexFromObjectName(edited_object); //--- Abfrage des Spalten- und Zeilen-Index in dem Data-Array int vc =c+m_scrollh.CurrentPos(); int vr =r+m_scrollv.CurrentPos(); //--- Korrektur des Zeilenindex, falls auf eine Kopfzeile geklickt wird if(m_fix_first_row && r==0) vr=0; //--- Abfrage des eingegebenen Wertes string cell_text=m_columns[c].m_rows[r].Description(); //--- Falls sich der Wert der Zelle geändert hat if(cell_text!=m_vcolumns[vc].m_vrows[vr]) { //--- Abspeichern des Wertes in dem Array SetValue(vc,vr,cell_text); //--- Eine Nachricht darüber senden ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),0,string(vc)+"_"+string(vr)+"_"+cell_text); } //--- return(true); } //+-----------------------------------------------------------------+ //| Abfrage des Spaltenindex aus dem Objektnamen | //+-----------------------------------------------------------------+ int CTable::ColumnIndexFromObjectName(const string object_name) { ushort u_sep=0; string result[]; int array_size=0; //--- Den Code des Trennzeichens erhalten u_sep=::StringGetCharacter("_",0); //--- Den String aufteilen ::StringSplit(object_name,u_sep,result); array_size=::ArraySize(result)-1; //--- Überprüfung der Überschreitung des ABCs if(array_size-3<0) { ::Print(PREVENTING_OUT_OF_RANGE); return(WRONG_VALUE); } //--- Rückgabe des Index des Elementes return((int)result[array_size-3]); } //+-----------------------------------------------------------------+ //| Abfrage des Zeilenindex aus dem Objektnamen | //+-----------------------------------------------------------------+ int CTable::RowIndexFromObjectName(const string object_name) { ushort u_sep=0; string result[]; int array_size=0; //--- Den Code des Trennzeichens erhalten u_sep=::StringGetCharacter("_",0); //--- Den String aufteilen ::StringSplit(object_name,u_sep,result); array_size=::ArraySize(result)-1; //--- Überprüfung der Überschreitung des ABCs if(array_size-2<0) { ::Print(PREVENTING_OUT_OF_RANGE); return(WRONG_VALUE); } //--- Rückgabe des Index des Elementes return((int)result[array_size-2]); }
Die CTable::HighlightSelectedItem() Methode wird gebraucht, falls das Hervorheben einer Zeile über den entsprechenden Modus aktiviert ist. Die hervorgehobene Zeile zeichnet sich durch andere Hintergrund- und Textfarben aus. Diese editierbaren Eigenschaften wurden bereits zuvor untersucht.
Zu Beginn der Methode werden zwei Überprüfungen vorgenommen (Schauen Sie sich dazu den nachfolgenden Programmcode an). Das Programm bricht hier ab, falls:
- Wenn der Modus für die Editierbarkeit einer Zelle aktiviert ist;
- Der Modus für das Hervorheben einer Zeile deaktiviert ist.
Wenn diese Überprüfungen erfolgreich waren, wird noch eine Verschiebung des Index durchgeführt, falls die Kopfzeilen der Spalten oder Zeilen fixiert sind. Jetzt sollten wir die ausgewählte Zeile finden und die passende Farbe für den Hintergrund der Zellen in der Doppel-Schleife mit, zwei Zählern (für Spalten und Zeilen), unter Berücksichtigung der aktuellen Position der Bild des Schiebereglers der Scrollbar, setzen. Für die Objekte der anderen Zeilen, wird die Farbe aus dem Array verwendet.
class CTable : public CElement { private: //--- Hervorheben der ausgewählten Zeile void HighlightSelectedItem(void); }; //+-----------------------------------------------------------------+ //| Hervorheben der ausgewählten Zeile | //+-----------------------------------------------------------------+ void CTable::HighlightSelectedItem(void) { //--- Abbrechen, falls einer der Modi ("Nur lesen", "Auswählbare Zeile") deaktiviert ist. if(!m_read_only || !m_selectable_row) return; //--- Verschieben um einen Index, falls der fixierte Kopfzeilen-Modus aktiviert ist int t=(m_fix_first_row) ? 1 : 0; int l=(m_fix_first_column) ? 1 : 0; //--- Abfrage der aktuellen Position des Schiebereglers der Horizontalen Scrollbar int h=m_scrollh.CurrentPos()+l; //--- Spalten for(int c=l; c<m_visible_columns_total; c++) { //--- Abfrage der aktuellen Position des Schiebereglers der vertikalen Scrollbar int v=m_scrollv.CurrentPos()+t; //--- Zeilen for(int r=t; r<m_visible_rows_total; r++) { //--- Verschiebung der Tabellendaten if(v>=t && v<m_rows_total) { //--- Einstellungen unter Berücksichtigung der ausgewählten Zeile color back_color=(m_selected_item==v) ? m_selected_row_color : m_vcolumns[h].m_cell_color[v]; color text_color=(m_selected_item==v) ? m_selected_row_text_color : m_vcolumns[h].m_text_color[v]; //--- Einstellung der Text- und Hintergrundfarbe der Zelle m_columns[c].m_rows[r].Color(text_color); m_columns[c].m_rows[r].BackColor(back_color); v++; } } //--- h++; } }
Ein weiterer nützlicher Modus, ist der Modus für das Hervorheben der Zeile, wenn sich der Mauszeiger darüber befindet. Hierfür wird die CTable::RowColorByHover() Methode verwendet. Sie beinhaltet auch einige Überprüfungen. Wenn diese nicht erfolgreich abgeschlossen werden, bricht das Programm an dieser Stelle der Methode ab. Ein Abbruch wird in den folgenden Fällen durchgeführt:
- Wenn der Modus für das Hervorheben einer Zeile, sobald sich der Mauszeiger darüber befindet, deaktiviert ist.
- Der Modus für die Editierbarkeit der Tabelle aktiviert ist.
- Wenn das Formular, zu welchem das Control hinzugefügt worden ist, blockiert ist
- Wenn einer der Scrollbar aktiv ist (Der Schieberegler bewegt sich gerade).
Wenn alle Überprüfungen erfolgreich durchlaufen worden sind, sollten wir in einer Doppelschleife durch alle Zellen laufen und deren Farbe in Abhängigkeit, über welcher Zelle sich der Mauszeiger gerade befindet, verändern. Die einzige Ausnahme ist eine hervorgehobene Zeile. Wenn wir an eine solche Zeile gelangen, wird nur der Zähler für die Zeile um einen erhöht aber die Zellen werden nicht geändert.
class CTable : public CElement { private: //--- Veränderung der Farbe der Zeile, wenn sich der Mauszeiger darüber befindet void RowColorByHover(const int x,const int y); }; //+-------------------------------------------------------------------------+ //| Veränderung der Zeilenfarbe, wenn sich der Mauszeiger darüber befindet | //+-------------------------------------------------------------------------+ void CTable::RowColorByHover(const int x,const int y) { //--- Abbrechen, wenn der Modus für das Hervorheben der Zeile deaktiviert ist oder wenn das Formular gesperrt ist if(!m_lights_hover || !m_read_only || m_wnd.IsLocked()) return; //--- Abbrechen, falls sich der Schieberegler einer Scrollbar gerade bewegt if(m_scrollv.ScrollState() || m_scrollh.ScrollState()) return; //--- Verschieben um einen Index, falls der fixierte Kopfzeilen-Modus aktiviert ist int t=(m_fix_first_row) ? 1 : 0; int l=(m_fix_first_column) ? 1 : 0; //--- Abfrage der aktuellen Position des Schiebereglers der Horizontalen Scrollbar int h=m_scrollh.CurrentPos()+l; //--- Spalten for(int c=l; c<m_visible_columns_total; c++) { //--- Abfrage der aktuellen Position des Schiebereglers der vertikalen Scrollbar int v=m_scrollv.CurrentPos()+t; //--- Zeilen for(int r=t; r<m_visible_rows_total; r++) { //--- Überprüfen, ob die Bandbreite des Arrays überschritten wird if(v>=t && v<m_rows_total) { //--- Überspringen, falls wir uns in dem "nur lesen"-Modus befinden, das Selektieren einer Zeile aktiviert ist und wir das selektierte Element erreicht haben if(m_selected_item==v && m_read_only && m_selectable_row) { v++; continue; } //--- Hervorheben der Zeile, falls sich der Mauszeiger darüber befindet if(x>m_columns[0].m_rows[r].X() && x<m_columns[m_visible_columns_total-1].m_rows[r].X2() && y>m_columns[c].m_rows[r].Y() && y<m_columns[c].m_rows[r].Y2()) { m_columns[c].m_rows[r].BackColor(m_cell_color_hover); } //--- Wiederherstellen der Standard Farbe, falls sich der Mauszeiger außerhalb des Bereiches dieser Zeile befindet else { if(v>=t && v<m_rows_total) m_columns[c].m_rows[r].BackColor(m_vcolumns[h].m_cell_color[v]); } //--- v++; } } //--- h++; } }
Wir haben nun alle Verwaltungs Methoden für die Tabelle untersucht. Sie können die Details des CTable::OnEvent() Eventhandler in dem nachfolgenden Programmcode ansehen. Bitte beachten Sie, das die Methode CTable::HighlightSelectedItem() auch nach dem Verarbeiten der Bewegung des vertikalen Scrollbar-Schiebereglers aufgerufen wird.
//+-----------------------------------------------------------------+ //| Event handler | //+-----------------------------------------------------------------+ void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Verarbeiten des Mauszeiger Bewegungs Events if(id==CHARTEVENT_MOUSE_MOVE) { //--- Abbrechen, falls das Element versteckt ist if(!CElement::IsVisible()) return; //---Koordinaten und der Status der linken Maustaste int x=(int)lparam; int y=(int)dparam; m_mouse_state=(bool)int(sparam); CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); //--- Falls die Scrollbar aktiv ist if(m_scrollv.ScrollBarControl(x,y,m_mouse_state) || m_scrollh.ScrollBarControl(x,y,m_mouse_state)) //--- Verschieben der Tabelle UpdateTable(); //--- Hervorheben der ausgewählten Zeile HighlightSelectedItem(); //--- Veränderung der Farbe der Zeile, wenn sich der Mauszeiger darüber befindet RowColorByHover(x,y); return; } //--- Verarbeitung von Klicks auf Objekte if(id==CHARTEVENT_OBJECT_CLICK) { //--- Falls eine Zeile der Tabelle geklickt wurde if(OnClickTableRow(sparam)) { //--- Hervorheben der ausgewählten Zeile HighlightSelectedItem(); return; } //--- Falls ein Scrollbar-Button geklickt wurde if(m_scrollv.OnClickScrollInc(sparam) || m_scrollv.OnClickScrollDec(sparam) || m_scrollh.OnClickScrollInc(sparam) || m_scrollh.OnClickScrollDec(sparam)) { //--- Aktualisierung der Daten der Tabelle unter Berücksichtigung der letzten Veränderungen UpdateTable(); //--- Hervorheben der ausgewählten Zeile HighlightSelectedItem(); return; } return; } //--- Verarbeiten des Events über die Veränderung des Wertes in dem Eingabefeld if(id==CHARTEVENT_OBJECT_ENDEDIT) { OnEndEditCell(sparam); //--- Zurücksetzen der Farben der Tabelle ResetColors(); return; } }
Test der Edit-Box Tabelle
Nun ist alles bereit für einen Test der Editbox-Tabelle Kopieren Sie den Expert Advisor aus dem vorangegangenen Beispiel und entfernen Sie alle Elemente, die mit der Text-Label-Tabelle in Zusammenhang stehen. Erzeugen Sie nun in der benutzerdefinierten CProgram Klasse die Instanz der CTable Klasse und deklarieren Sie die Methoden für das Erzeugen der Tabelle:
class CProgram : public CWndEvents { private: //--- Die Editbox-Tabelle CTable m_table; //--- private: //--- Die Editbox-Tabelle #define TABLE1_GAP_X (1) #define TABLE1_GAP_Y (42) bool CreateTable(void); };
Lassen Sie uns eine Tabelle erzeugen, die aus 100 Spalten und 1000 Zeilen besteht. Die Anzahl der sichtbaren Spalten ist 6, Und die Anzahl der sichtbaren Zeilen ist 15. Lassen Sie uns die Überschriften fixieren (Die erste Zeile und die erste Spalte), damit sich diese nicht bewegen, falls der Schieberegler einer Scrollbar bewegt wird. Wir aktivieren die Modi für das Selektieren einer Zeile und das Hervorheben einer Zeile, sobald sich der Mauszeiger darüber befindet.
Nach der Erzeugung des Controls, werden die Tabellen-Arrays mit Daten gefüllt und formatiert. Wir legen beispielhaft die ALIGN_RIGHT Ausrichtungsmethode für die erste Spalte fest. Lassen Sie uns die Zellen der Zeilen im "Zebra" Stil zeichnen, und die Farbe des Textes der Spalten wird in rot und blau vorgenommen. Stellen Sie sicher, dass Sie dieTabelle aktualisieren, damit die Veränderungen sichtbar werden.
//+-----------------------------------------------------------------+ //| Erzeugung der Tabelle | //+-----------------------------------------------------------------+ bool CProgram::CreateTable(void) { #define COLUMNS1_TOTAL (100) #define ROWS1_TOTAL (1000) //--- Abspeichern des Pointers des Formulars m_table.WindowPointer(m_window1); //--- Koordinaten int x=m_window1.X()+TABLE1_GAP_X; int y=m_window1.Y()+TABLE1_GAP_Y; //--- Die Anzahl der sichtbaren Spalten und Zeilen int visible_columns_total =6; int visible_rows_total =15; //--- Vor der Erzeugung die Eigenschaften festlegen m_table.XSize(600); m_table.RowYSize(20); m_table.FixFirstRow(true); m_table.FixFirstColumn(true); m_table.LightsHover(true); m_table.SelectableRow(true); m_table.TextAlign(ALIGN_CENTER); m_table.HeadersColor(C'255,244,213'); m_table.HeadersTextColor(clrBlack); m_table.GridColor(clrLightGray); m_table.CellColorHover(clrGold); m_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL); m_table.VisibleTableSize(visible_columns_total,visible_rows_total); //--- Erzeugung des Control-Elementes if(!m_table.CreateTable(m_chart_id,m_subwin,x,y)) return(false); //--- Einfügen in die Tabelle: // Die erste Zelle ist leer m_table.SetValue(0,0,"-"); //--- Die Überschriften der Spalten for(int c=1; c<COLUMNS1_TOTAL; c++) { for(int r=0; r<1; r++) m_table.SetValue(c,r,"SYMBOL "+string(c)); } //--- Die Überschriften der Zeilen, die Ausrichtung ist Rechts for(int c=0; c<1; c++) { for(int r=1; r<ROWS1_TOTAL; r++) { m_table.SetValue(c,r,"PARAMETER "+string(r)); m_table.TextAlign(c,r,ALIGN_RIGHT); } } //--- Daten- und Tabellen-Formatierung (Hintergrund und Zellenfarben) for(int c=1; c<COLUMNS1_TOTAL; c++) { for(int r=1; r<ROWS1_TOTAL; r++) { m_table.SetValue(c,r,string(c)+":"+string(r)); m_table.TextColor(c,r,(c%2==0)? clrRed : clrRoyalBlue); m_table.CellColor(c,r,(r%2==0)? clrWhiteSmoke : clrWhite); } } //--- Aktualisierung der Tabelle um die Änderungen sichtbar zu machen m_table.UpdateTable(); //--- Hinzufügen des Objektes zu dem Array aller Objektgruppen CWndContainer::AddToElementsArray(0,m_table); return(true); }
Die CProgram::CreateTable() Methode sollte in der Hauptmethode der Anwendung aufgerufen werden. (Sehen Sie sich dazu die abgekürzte Version des nachfolgenden Programmcodes an):
//+----------------------------------------------------------------+ //| Erzeugung des Expert-Bedienfeldes | //+----------------------------------------------------------------+ bool CProgram::CreateExpertPanel(void) { //--- Erzeugen des Formulars 1 für die Controls //--- Erzeugen der Controls: // Hauptmenü //--- Kontextmenüs //--- Erzeugen der Statuszeile //--- Editbox-Tabelle if(!CreateTable()) return(false); //--- Neuzeichnen des Charts m_chart.Redraw(); return(true); }
Kompilieren Sie das Programm und lassen Sie das auf einem Chart laufen. Wenn alles korrekt ausgefüllt worden ist, dann erhalten wir ein Ergebnis, so wie es in den nachfolgenden Screenshot gezeigt wird:
Abbildung 4. Test der Editbox-Tabelle
Alles arbeitet einwandfrei. Aber wenn Sie einen Text, der mehr als 63 Zeichen lang ist, in einer einzigen Zelle eingeben, dann wird der Text nicht vollständig angezeigt. Alle grafischen Objekte in dem Terminal, die die Möglichkeit haben einen text anzuzeigen, haben eine Begrenzung von 63 Symbolen. Wir verwenden die CCanvas Klasse für das Zeichnen, um dieses Problem zu umgehen. Diese Klasse beinhaltet Methoden für die Darstellung von Text ohne irgendwelche Einschränkungen. Wir haben diese Klasse schon bei einigen Controls angewendet (Trendy mir und Kontextmenü). Artikel:
- Grafische Interfaces II: Die Trennlinien und Context-Menüelemente (Kapitel 2)
- Grafische Interfaces IV: Informierende Interface-Elemente (Kapitel 1)
Da 63 Zeichen sehr oft nicht ausreichend sind, wird die dritte Tabelle unter Verwendung der CCanvas Klasse gezeichnet.
Das Rendered Tabellen-Control
Eine gerenderte Tabelle besitzt keine Begrenzungen, hinsichtlich der Anzahl der Zeichen für jede Zelle. Zudem kann die Breite von jeder Spalte konfiguriert werden, ohne den sichtbaren Bereich der Tabelle ändern zu müssen. Dieses ist bei den anderen Tabellentypen, die wir zuvor besprochen haben, sehr schwierig. Somit können wir schon einige Vorteile der gerenderten Tabelle aufzeigen:
- Keine Begrenzung der Anzahl der Symbole pro Zelle;
- Jede Spaltenbreite kann separat festgelegt werden;
- Es wird nur ein Objekt (OBJ_BITMAP_LABEL) für die Erzeugung der Tabelle verwendet, anstelle von verschiedenen Objekten, wie es bei Text-Labels (OBJ_LABEL) oder Editboxen (OBJ_EDIT) der Fall ist.
Text-Label Tabellen bestehend aus den folgenden Komponenten:
- Hintergrund
- Gerenderte Tabelle
- Vertikale Scrollbar.
- Horizontale Scrollbar.
Abbildung 5. Komponenten einer gerenderten Tabelle
Lassen Sie uns den Programmcode der Klasse für die Erzeugung einer solchen Tabelle genauer untersuchen.
Entwicklung der CCanvasTable Klasse
Lassen Sie uns die CTOptions Struktur erzeugen, um darin die Werte der Tabelle und die Eigenschaften abzuspeichern:
//+-----------------------------------------------------------------+ //| Klasse für die Erzeugung einer gerenderten Tabelle | //+-----------------------------------------------------------------+ class CCanvasTable : public CElement { private: //--- Array für die Werte und Eigenschaften der Tabelle struct CTOptions { string m_vrows[]; int m_width; ENUM_ALIGN_MODE m_text_align; }; CTOptions m_vcolumns[]; };
Die CTOptions Variablen der Struktur werden mit den Standardwerten initialisiert, wenn die grundlegende Größe der Tabelle (Gesamt Anzahl der Spalten und Zeilen) gesetzt wird. Wir legen die breite aller Spalten auf 100 Pixels fest, und die Ausrichtung in den Zellen ist ALIGN_CENTER.
class CCanvasTable : public CElement { public: //--- Festlegen der Größe der Tabelle void TableSize(const int columns_total,const int rows_total); }; //+-----------------------------------------------------------------+ //| Legt die Größe der Tabelle fest | //+-----------------------------------------------------------------+ void CCanvasTable::TableSize(const int columns_total,const int rows_total) { //--- Es muss mindestens eine Spalte geben m_columns_total=(columns_total<1) ? 1 : columns_total; //--- Es muss mindestens zwei Zeilen geben m_rows_total=(rows_total<2) ? 2 : rows_total; //--- Festlegen der Größe der Spalten-Arrays ::ArrayResize(m_vcolumns,m_columns_total); //--- Festlegen der Größe der Zeilen-Arrays for(int i=0; i<m_columns_total; i++) { ::ArrayResize(m_vcolumns[i].m_vrows,m_rows_total); //--- Initialisierung der Spalteneigenschaften mit den Standardwerten m_vcolumns[i].m_width =100; m_vcolumns[i].m_text_align =ALIGN_CENTER; } }
Lassen Sie uns nur die Methoden, die es uns erlauben, die Breite oder die Ausrichtung des Textes von einigen Spalten (falls nötig) festzulegen, nachdem die Größe der Tabelle gesetzt wurde. Um dieses zu tun, müssen Sie lediglich ihr Array initialisieren und es der entsprechenden Methode übergeben. Nachfolgend wird ein Beispiel gezeigt:
class CCanvasTable : public CElement { public: //--- Festlegen der (1) Ausrichtung des Textes und (2) der Breite für jede Spalte void TextAlign(const ENUM_ALIGN_MODE &array[]); void ColumnsWidth(const int &array[]); }; //+----------------------------------------------------------------+ //| Füllt das array mit den Textausrichtungs-Modi | //+----------------------------------------------------------------+ void CCanvasTable::TextAlign(const ENUM_ALIGN_MODE &array[]) { int total=0; int array_size=::ArraySize(array); //--- Abbrechen, falls ein Array der Größe 0 übergeben wurde if(array_size<1) return; //--- Einstellen des Wertes, um ein Überschreiten der Bandbreite des Arrays zu verhindern total=(array_size<m_columns_total)? array_size : m_columns_total; //--- Abspeichern der Werte in der Struktur for(int c=0; c<total; c++) m_vcolumns[c].m_text_align=array[c]; } //+----------------------------------------------------------------+ //| Sylt das Array mit den Spaltenbreiten | //+----------------------------------------------------------------+ void CCanvasTable::ColumnsWidth(const int &array[]) { int total=0; int array_size=::ArraySize(array); //--- Abbrechen, falls ein Array der Größe 0 übergeben wurde if(array_size<1) return; //--- Einstellen des Wertes, um ein Überschreiten der Bandbreite des Arrays zu verhindern total=(array_size<m_columns_total)? array_size : m_columns_total; //--- Abspeichern der Werte in der Struktur for(int c=0; c<total; c++) m_vcolumns[c].m_width=array[c]; }
Bevor wir mit der Erzeugung der Tabelle fortfahren, müssen wir die Gesamtgröße, sowie die Größe des sichtbaren Bereiches, relativ zu den angegebenen Parametern (Breite aller Spalten, Höhe aller Zeilen und die Anwesenheit von Scrollbars) angeben. Dafür schreiben wir die nachfolgend aufgezeigte Methode CCanvasTable::CalculateTableSize():
class CCanvasTable : public CElement { private: //--- Gesamtgröße und die Größe des sichtbaren Bereiches der Tabelle int m_table_x_size; int m_table_y_size; int m_table_visible_x_size; int m_table_visible_y_size; //--- private: //--- Berechnung der Größe der Tabelle void CalculateTableSize(void); }; //+-----------------------------------------------------------------+ //| Berechnung der Größe der Tabelle | //+-----------------------------------------------------------------+ void CCanvasTable::CalculateTableSize(void) { //--- Berechnung der Gesamtbreite der Tabelle m_table_x_size=0; for(int c=0; c<m_columns_total; c++) m_table_x_size=m_table_x_size+m_vcolumns[c].m_width; //--- Breite der Tabelle mit einer vertikalen Scrollbar int x_size=(m_rows_total>m_visible_rows_total) ? m_x_size-m_scrollh.ScrollWidth() : m_x_size-2; //--- Falls die Breite aller Spalten kleiner ist als die Breite der Tabelle, dann wird die Breite der Tabelle verwendet if(m_table_x_size<m_x_size) m_table_x_size=x_size; //--- Berechnung der Gesamthöhe der Tabelle m_table_y_size=m_cell_y_size*m_rows_total-(m_rows_total-1); //--- Festlegen der Rahmengröße, um einen Teil des Bildes anzuzeigen (sichtbarer Bereich der Tabelle) m_table_visible_x_size=x_size; m_table_visible_y_size=m_cell_y_size*m_visible_rows_total-(m_visible_rows_total-1); //--- Falls es eine horizontale Scrollbar gibt, dann wird die Größe des Controls entlang der y-Achse angepasst int y_size=m_cell_y_size*m_visible_rows_total+2-(m_visible_rows_total-1); m_y_size=(m_table_x_size>m_table_visible_x_size) ? y_size+m_scrollh.ScrollWidth()-1 : y_size; }
Nach der Berechnung der Größe der Tabelle und der Erzeugung des Canvas, müssen wir eine Methode für das Zeichnen des Tabellengitters und der Zellen-Texte entwickeln. Für das Zeichnen des Gitters, schreiben wir die CCanvasTable::DrawGrid() Methode. Zunächst werden in einer ersten Schleife die horizontalen Gitterlinien gezeichnet und anschließend in einer zweiten Schleife die vertikalen Gitterlinien.
class CCanvasTable : public CElement { private: //--- Farbe des Gitters color m_grid_color; //--- Die Größe (Höhe) der Zellen int m_cell_y_size; //--- public: //--- Farbe des Gitters void GridColor(const color clr) { m_grid_color=clr; } //--- private: //--- Zeichnen des Gitters void DrawGrid(void); }; //+-----------------------------------------------------------------+ //| Zeichnen des Gitters | //+-----------------------------------------------------------------+ void CCanvasTable::DrawGrid(void) { //--- Farbe des Gitters uint clr=::ColorToARGB(m_grid_color,255); //--- Die Größe des Canvas für das Zeichnen int x_size =m_canvas.XSize()-1; int y_size =m_canvas.YSize()-1; //--- Koordinaten int x1=0,x2=0,y1=0,y2=0; //--- Horizontale Linien x1=0; y1=0; x2=x_size; y2=0; for(int i=0; i<=m_rows_total; i++) { m_canvas.Line(x1,y1,x2,y2,clr); y2=y1+=m_cell_y_size-1; } //--- Vertikale Linien x1=0; y1=0; x2=0; y2=y_size; for(int i=0; i<m_columns_total; i++) { m_canvas.Line(x1,y1,x2,y2,clr); x2=x1+=m_vcolumns[i].m_width; } //--- Rechts x1=x_size; y1=0; x2=x_size; y2=y_size; m_canvas.Line(x1,y1,x2,y2,clr); }
Die CCanvasTable::DrawText() Methode für das Zeichen von Text ist ein wenig komplizierter als die Methode für das Zeichnen des Gitters. Wir sollten nicht nur die Ausrichtung des Textes der aktuellen Spalte berücksichtigen sondern auch die der vorherigen Spalte, um die Einzüge korrekt berechnen zu können. Wir legen einen Einzug von 10 Pixeln von der Ecke einer Zelle für die rechte und linke Ausrichtung fest. Der Einzug von der Oberkante einer Zelle ist 3 Pixel. Nachfolgend sehen Sie den detaillierten Programmcode dieser Methode:
class CCanvasTable : public CElement { private: //--- Die Textfarbe color m_cell_text_color; //--- public: //--- Textfarbe der Tabelle void TextColor(const color clr) { m_cell_text_color=clr; } //--- private: //--- Zeichnen des Textes void DrawText(void); }; //+-----------------------------------------------------------------+ //| Zeichnen des Textes | //+-----------------------------------------------------------------+ void CCanvasTable::DrawText(void) { //--- Für die Berechnung der Koordinaten und des Offsets int x =0; int y =0; uint text_align =0; int column_offset =0; int cell_x_offset =10; int cell_y_offset =3; //--- Die Textfarbe uint clr=::ColorToARGB(m_cell_text_color,255); //--- Eigenschaften der Schrift m_canvas.FontSet(FONT,-80,FW_NORMAL); //--- Spalten for(int c=0; c<m_columns_total; c++) { //--- Berechnung des Offsets für die erste Spalte if(c==0) { //--- Die Ausrichtung Inhalt einer Zelle basiert auf dem Modus der für die Spalte gesetzt ist switch(m_vcolumns[0].m_text_align) { //--- Mittig case ALIGN_CENTER : column_offset=column_offset+m_vcolumns[0].m_width/2; x=column_offset; break; //--- Rechts case ALIGN_RIGHT : column_offset=column_offset+m_vcolumns[0].m_width; x=column_offset-cell_x_offset; break; //--- Links case ALIGN_LEFT : x=column_offset+cell_x_offset; break; } } //--- Die Berechnung der Offset für alle Spalten mit Ausnahme der ersten else { //--- Die Ausrichtung Inhalt einer Zelle basiert auf dem Modus der für die Spalte gesetzt ist switch(m_vcolumns[c].m_text_align) { //--- Mittig case ALIGN_CENTER : //--- Die Berechnung des Offset relativ zu der Ausrichtung in der vorherigen Spalte switch(m_vcolumns[c-1].m_text_align) { case ALIGN_CENTER : column_offset=column_offset+(m_vcolumns[c-1].m_width/2)+(m_vcolumns[c].m_width/2); break; case ALIGN_RIGHT : column_offset=column_offset+(m_vcolumns[c].m_width/2); break; case ALIGN_LEFT : column_offset=column_offset+m_vcolumns[c-1].m_width+(m_vcolumns[c].m_width/2); break; } //--- x=column_offset; break; //--- Rechts case ALIGN_RIGHT : //--- Die Berechnung des Offset relativ zu der Ausrichtung in der vorherigen Spalte switch(m_vcolumns[c-1].m_text_align) { case ALIGN_CENTER : column_offset=column_offset+(m_vcolumns[c-1].m_width/2)+m_vcolumns[c].m_width; x=column_offset-cell_x_offset; break; case ALIGN_RIGHT : column_offset=column_offset+m_vcolumns[c].m_width; x=column_offset-cell_x_offset; break; case ALIGN_LEFT : column_offset=column_offset+m_vcolumns[c-1].m_width+m_vcolumns[c].m_width; x=column_offset-cell_x_offset; break; } //--- break; //--- Links case ALIGN_LEFT : //--- Die Berechnung des Offset relativ zu der Ausrichtung in der vorherigen Spalte switch(m_vcolumns[c-1].m_text_align) { case ALIGN_CENTER : column_offset=column_offset+(m_vcolumns[c-1].m_width/2); x=column_offset+cell_x_offset; break; case ALIGN_RIGHT : x=column_offset+cell_x_offset; break; case ALIGN_LEFT : column_offset=column_offset+m_vcolumns[c-1].m_width; x=column_offset+cell_x_offset; break; } //--- break; } } //--- Zeilen for(int r=0; r<m_rows_total; r++) { //--- y+=(r>0) ? m_cell_y_size-1 : cell_y_offset; //--- switch(m_vcolumns[c].m_text_align) { case ALIGN_CENTER : text_align=TA_CENTER|TA_TOP; break; case ALIGN_RIGHT : text_align=TA_RIGHT|TA_TOP; break; case ALIGN_LEFT : text_align=TA_LEFT|TA_TOP; break; } //--- Zeichnen des Textes m_canvas.TextOut(x,y,m_vcolumns[c].m_vrows[r],clr,text_align); } //--- Zurücksetzen der y-Koordinate für den nächsten Durchlauf y=0; } }
In den ersten beiden Tabellentypen, haben wir untersucht, dass das verschieben über die Scrollbar, durch das Verändern der Werte der Objekte, die sich in dem sichtbaren Bereich der Tabelle befinden, geschieht (Text-Labes und Editboxen). Hier verschieben wir den rechteckigen Rahmen für die Sichtbarkeit des Bildes. Mit anderen Worten, die Größe der Tabelle (Bild) ist anfänglich gleich der Summe aller Spalten und die Höhe aller Zeilen. Dieses ist die Größe des original Bildes, in welchem der Rahmen für die Sichtbarkeit bewegt werden kann. Die Größe des Rahmens für die Sichtbarkeit kann zu jederzeit geändert werden, aber hier wird Sie direkt nach der Erzeugung des Controls in der CCanvasTable::CreateCells() Methode festgelegt.
Die Größe des Rahmens kann über die Eigenschaften OBJPROP_XSIZE und OBJPROP_YSIZE festgelegt werden und die Verschiebung des Rahmens(innerhalb des Bildes) kann über die Eigenschaften OBJPROP_XOFFSET und OBJPROP_YOFFSET gesteuert werden. (Sehen Sie sich dazu das nachfolgende Beispiel an):
//--- Festlegen der Größe des sichtbaren Bereiches ::ObjectSetInteger(m_chart_id,name,OBJPROP_XSIZE,m_table_visible_x_size); ::ObjectSetInteger(m_chart_id,name,OBJPROP_YSIZE,m_table_visible_y_size); //--- Festlegen des Offsets für den Rahmen innerhalb des Bildes entlang der X und Y-Achse ::ObjectSetInteger(m_chart_id,name,OBJPROP_XOFFSET,0); ::ObjectSetInteger(m_chart_id,name,OBJPROP_YOFFSET,0);
Lassen Sie uns die einfache Methode CCanvasTable::ShiftTable() für die Verschiebung des Rahmens, relativ zu der aktuellen Position des Schiebereglers der Scrollbar, entwickeln. Die vertikale Verschiebung ist gleich der Höhe der Zeile, aber die horizontale Verschiebung wird in Pixeln ausgeführt (Sehen Sie sich dazu das nachfolgende Programmbeispiel an):
class CCanvasTable : public CElement { public: //--- Verschiebung der Tabelle, relativ zu den Positionen der Scrollbars void ShiftTable(void); }; //+-----------------------------------------------------------------+ //| Verschiebung der Tabelle relativ zu den Scrollbars | //+-----------------------------------------------------------------+ void CCanvasTable::ShiftTable(void) { //--- Abfrage der aktuellen Positionen der Schieberegler der vertikalen und horizontalen Scrollbars int h=m_scrollh.CurrentPos(); int v=m_scrollv.CurrentPos(); //--- Berechnung der Position der Tabelle in Relation zu den Schiebereglern der Scrollbars long c=h; long r=v*(m_cell_y_size-1); //--- Verschieben der Tabelle ::ObjectSetInteger(m_chart_id,m_canvas.Name(),OBJPROP_XOFFSET,c); ::ObjectSetInteger(m_chart_id,m_canvas.Name(),OBJPROP_YOFFSET,r); }
Die generelle CCanvasTable::DrawTable() Methode für das Zeichnen der Tabelle, sieht wie folgt aus:
class CCanvasTable : public CElement { public: //--- Zeichne die Tabelle unter Berücksichtigung der letzten Änderungen void DrawTable(void); }; //+-----------------------------------------------------------------+ //| Zeichnen der Tabelle | //+-----------------------------------------------------------------+ void CCanvasTable::DrawTable(void) { //--- Den Hintergrund transparent machen m_canvas.Erase(::ColorToARGB(clrNONE,0)); //--- Zeichnen des Gitters DrawGrid(); //--- Zeichnen des Textes DrawText(); //--- Darstellen der zuletzt gezeichneten Veränderungen m_canvas.Update(); //--- Verschiebung der Tabelle in Relation zu den Scrollbars ShiftTable(); }
Nun ist alles dafür bereit, um diese Tabelle zu testen.
Test der gerenderten-Tabelle
Machen Sie eine Kopie des vorangegangenen EAs und entfernen Sie alle Elemente, die in Zusammenhang mit der CTable Tabelle stehen. Erzeugen Sie nun in der benutzerdefinierten CProgram Klassse eine Instanz der CCanvasTable Klasse und deklarieren Sie die (1) CProgram::CreateCanvasTable() Methode für die Erzeugung der Tabelle, sowie die (2) Einzüge, so wie es in dem nachfolgenden Beispiel gezeigt wird:
class CProgram : public CWndEvents { private: //--- Gerenderte Tabelle CCanvasTable m_canvas_table; //--- private: //--- Gerenderte Tabelle #define TABLE1_GAP_X (1) #define TABLE1_GAP_Y (42) bool CreateCanvasTable(void); };
Lassen Sie uns eine Tabelle mit 15 Spalten und 1000 Zeilen erzeugen. Die Anzahl der sichtbaren Zeilen ist 16. Wir müssen die Anzahl der sichtbaren Spalten nicht festlegen, da die horizontale Verschiebung in Pixeln durchgeführt wird. In diesem Fall sollte die Breite des sichtbaren Bereichs der Tabelle explizit angegeben werden. Lassen Sie sie uns auf 601 Pixel festlegen.
Als Beispiel, lassen Sie uns die Breite aller Spalten (mit Ausnahme der ersten zwei) auf 70 Pixel festlegen. Die Breiten der ersten und zweiten Spalte werden auf 100 und 90 Pixel festgelegt. In allen Spalten (mit Ausnahme der ersten drei), wird der Text mittig dargestellt. In der ersten und dritten Spalte ist die Ausrichtung rechts, während in der Zweiten die Ausrichtung links ist. Der vollständige Programmcode der CProgram::CreateCanvasTable() Methode wird in dem nachfolgenden Programmcode dargestellt:
//+-----------------------------------------------------------------+ //| Erzeugung der gerenderten Tabelle | //+-----------------------------------------------------------------+ bool CProgram::CreateCanvasTable(void) { #define COLUMNS1_TOTAL 15 #define ROWS1_TOTAL 1000 //--- Abspeichern des Pointers des Formulars m_canvas_table.WindowPointer(m_window1); //--- Koordinaten int x=m_window1.X()+TABLE1_GAP_X; int y=m_window1.Y()+TABLE1_GAP_Y; //--- Anzahl der sichtbaren Zeilen int visible_rows_total=16; //--- Array mit Spaltenbreiten int width[COLUMNS1_TOTAL]; ::ArrayInitialize(width,70); width[0]=100; width[1]=90; //--- Array für die Textausrichtung in den Spalten ENUM_ALIGN_MODE align[COLUMNS1_TOTAL]; ::ArrayInitialize(align,ALIGN_CENTER); align[0]=ALIGN_RIGHT; align[1]=ALIGN_LEFT; align[2]=ALIGN_RIGHT; //--- Vor der Erzeugung die Eigenschaften festlegen m_canvas_table.XSize(601); m_canvas_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL); m_canvas_table.VisibleTableSize(0,visible_rows_total); m_canvas_table.TextAlign(align); m_canvas_table.ColumnsWidth(width); m_canvas_table.GridColor(clrLightGray); //--- Tabelle mit Daten auffüllen for(int c=0; c<COLUMNS1_TOTAL; c++) for(int r=0; r<ROWS1_TOTAL; r++) m_canvas_table.SetValue(c,r,string(c)+":"+string(r)); //--- Erzeugen des Controls if(!m_canvas_table.CreateTable(m_chart_id,m_subwin,x,y)) return(false); //--- Hinzufügen des Objektes zu dem Array aller Objektgruppen CWndContainer::AddToElementsArray(0,m_canvas_table); return(true); }
Der Aufruf der Methode muss in der Hauptmethode für die Erzeugung des grafischen Interfaces stattfinden.
//+-----------------------------------------------------------------+ //| Erzeugung des Trading-Panels | //+-----------------------------------------------------------------+ bool CProgram::CreateExpertPanel(void) { //--- Erzeugen des Formulars 1 für die Controls //--- Erzeugen der Controls: // Hauptmenü //--- Kontextmenüs //--- Erzeugen der Statuszeile //--- Erzeugung der gerenderten Tabelle if(!CreateCanvasTable()) return(false); //--- Neuzeichnen des Charts m_chart.Redraw(); return(true); }
Kompilieren Sie das Programm und lassen Sie das auf einem Chart laufen. Der nachfolgende Screenshot zeigt das Ergebnis:
Abbildung 6. Test der gerenderten Tabelle in einem EA
In den fünften Kapitel des ersten Teil dieser Serie, haben wir die Verwendung von Formularen in Skripten getestet. Tabellen ohne Scrollbars können bei dieser Art von MQL Anwendungen verwendet werden. Als Beispiel können Sie eine gerenderte Tabelle dem Skript des Formulars hinzufügen und die Daten alle 250 Millisekunden aktualisieren. Fügen Sie die zuvor gezeigte Tabelle der benutzerdefinierten Klasse hinzu. Zudem muss noch der Programmcode dem CProgram::OnEvent() Skript Eventhandler, wie nachfolgend gezeigt, hinzugefügt werden. Nun ändern sich mit dem angegebenen Zeitintervall, die Daten innerhalb der zweiten Spalte.
//+-----------------------------------------------------------------+ //| Events | //+-----------------------------------------------------------------+ void CProgram::OnEvent(const int milliseconds) { static int count =0; // Counter string str =""; // Header row //--- Die Kopfzeile zeigt den Prozess switch(count) { case 0 : str="SCRIPT PANEL"; break; case 1 : str="SCRIPT PANEL ."; break; case 2 : str="SCRIPT PANEL .."; break; case 3 : str="SCRIPT PANEL ..."; break; } //--- Aktualisierung der Kopfzeile m_window.CaptionText(str); //--- Änderung der Daten der ersten Spalte for(int r=0; r<13; r++) m_canvas_table.SetValue(1,r,string(::rand())); //--- Anzeigen der neuen Tabellendaten m_canvas_table.DrawTable(); //--- Neuzeichnen des Charts m_chart.Redraw(); //--- Erhöhung des Zählers count++; //--- Zurücksetzen auf Null, falls 3 überschritten wird if(count>3) count=0; //--- Pause ::Sleep(milliseconds); }
Kompilieren Sie das Programm und starten Sie dieses Skript auf einem Chart Sie sollten nun das folgende sehen:
Abbildung 7. Test der gerenderten Tabelle innerhalb eines Skriptes
Wir haben die Entwicklung der CCanvasTable Klasse für die Erzeugung einer gerenderten Tabelle abgeschlossen. Nun ist es Zeit für einige vorläufige Ergebnisse.
Schlussfolgerung
In diesen Artikel haben wir drei Klassen für die Erzeugung eines wichtigen Interface-Elementes, die Tabelle, besprochen. Jede dieser Klassen hat ihre besonderen Eigenschaften, die für bestimmte Fälle am besten geeignet sind. Zum Beispiel bietet die CTable Klasse die Möglichkeit der Entwicklung einer Tabelle mit editierbaren Boxen, wobei diese formatierbar sind, was sie sehr benutzerfreundlich aussehen lässt. Die CCanvasTable Klasse erlaubt es dagegen, die Begrenzung der Anzahl von Zeichen innerhalb einer Zelle zu umgehen und bietet zudem die Möglichkeit, für jede Spalte eine eigene Breite angeben zu können. Dieses sind nicht die endgültigen Versionen der Tabellen. Falls nötig, können Sie später noch erweitert werden.
In dem nächsten Artikel werden wir Klassen für die Entwicklung von Tab-Controls besprechen, die ebenso häufig in graphischen Interfaces verwendet werden.
Sie können das gesamte Material des siebten Teils herunterladen und testen. Wenn Sie fragen zur Verwendung dieses Materials haben, dann können Sie zunächst auf die detaillierte Beschreibung in dem Artikel zu dieser Bibliothek zurückgreifen oder Sie stellen Ihre Frage(n) in den Kommentaren zu diesem Artikel.
Liste der Artikel (Kapitel) des siebten Teils:
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/2500
- 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.