
Grafische Interfaces X: Elemente der Zeit, Listen von Kontrollkästchen und das Sortieren von Tabellen (build 6)
Inhalt
- Einführung
- Die Elemente der Zeit
- Die Klasse zum Erstellen der Zeitelemente
- Die Listen von Kontrollkästchen
- Die Klasse zum Erstellen der Listen von Kontrollkästchen
- Das Sortieren von Tabellen
- Weitere Aktualisierungen der Bibliothek
- Anwendung zum Testen der Kontrollelemente
- Schlussfolgerung
Einführung
Um ein besseres Verständnis von dem Zwecke dieser Bibliothek zu erhalten, lesen Sie bitte den ersten Artikel: Grafische Interfaces I: Vorbereitung der Bibliotheksstruktur (Kapitel 1). Sie finden eine Liste von Artikeln mit Verweisen am Ende jeden Kapitels. Dort können Sie auch die komplette, aktuelle Version der Bibliothek zum derzeitigen Entwicklungsstand herunterladen. Die Dateien müssen im gleichen Verzeichnis wie das Archiv platziert werden.
Die Bibliothek wird kontinuierlich weiterentwickelt. Zeitelemente und die Listen von Kontrollkästchen werden besprochen. Weiters verfügt die Klasse CTable jetzt über die Möglichkeit, Daten auf- oder absteigend zu sortieren. Diese und andere Aktualisierungen werden werden in diesem Artikel beschrieben.
Die Elemente der Zeit
Manchmal muss man Zeitspannen für einen Indikator, Expert oder grafische Schnittstellen spezifizieren. Manchmal entsteht diese Notwendigkeit im Laufe eines Handelstages. Mit dem Kalender und dem "Drop-Down" Kalender kann bereits ein Datum bestimmt werden, aber eben noch kein Zeitpunkt (Stunde und Minute).
Zählen wir mal alle Elemente zur Steuerung der Zeit auf:
- Hintergrund (Background)
- Symbol (Icon)
- Beschreibung (Description)
- Zwei Eingabefelder (Edit boxes)
Fig. 1. Komponenten der Zeitsteuerung.
Schauen wir uns diese Klasse einmal genauer an.
Die Klasse zum Erstellen der Zeitelemente
Erstellen Sie die Datei TimeEdit.mqh mit der Klasse CTimeEdit, die über alle Standardmethoden der Kontrollelemente verfügt und laden Sie sie in die Bibliothek (die Datei WndContainer.mqh). Unten sind die Eigenschaften der Kontrollelemente für einen Anpassung durch die Nutzer.
- Farbe des Hintergrunds des Kontrollelementes
- Symbole für den aktiven und den blockierten Zustand
- Ränder entlang der beiden Achsen (x, y)
- Beschreibungstext des Kontrollelementes
- Ränder der Textfelder entlang der beiden Achsen (x, y)
- Textfarben der verschiedenen Zustände des Kontrollelementes
- Breit des Eingabefeldes
- Ränder der Eingabefelder entlang der beiden Achsen (x, y)
- Zustände des Kontrollelements (aktiv/blockiert)
- Art der Rücksetzung der Werte in den Eingabefeldern
//| Klasse zum Erstellen der Zeitelemente |
//+------------------------------------------------------------------+
class CTimeEdit : public CElement
{
private:
//--- Farbe des Hintergrunds des Kontrollelementes
color m_area_color;
//--- Symbole für den aktiven und den blockierten Zustand
string m_icon_file_on;
string m_icon_file_off;
//--- Ränder der Symbole
int m_icon_x_gap;
int m_icon_y_gap;
//--- Beschreibungstext des Kontrollelementes
string m_label_text;
//--- Ränder der Textfelder
int m_label_x_gap;
int m_label_y_gap;
//--- Textfarbe in verschiedenen Zuständen
color m_label_color;
color m_label_color_hover;
color m_label_color_locked;
color m_label_color_array[];
//--- Größe der Eingabefelder
int m_edit_x_size;
//--- Ränder der Eingabefelder
int m_edit_x_gap;
int m_edit_y_gap;
//--- Zustände des Kontrollelements (aktiv/blockiert)
bool m_time_edit_state;
//--- Art der Rücksetzung der Werte
bool m_reset_mode;
//---
public:
//--- (1) Hintergrundfarbe, (2) Symbolränder
void AreaColor(const color clr) { m_area_color=clr; }
void IconXGap(const int x_gap) { m_icon_x_gap=x_gap; }
void IconYGap(const int y_gap) { m_icon_y_gap=y_gap; }
//--- (1) Beschreibungstext des Kontrollelementes, (2) Ränder der Textfelder
string LabelText(void) const { return(m_label.Description()); }
void LabelText(const string text) { m_label.Description(text); }
void LabelXGap(const int x_gap) { m_label_x_gap=x_gap; }
void LabelYGap(const int y_gap) { m_label_y_gap=y_gap; }
//--- Textfarben der verschiedenen Zustände
void LabelColor(const color clr) { m_label_color=clr; }
void LabelColorHover(const color clr) { m_label_color_hover=clr; }
void LabelColorLocked(const color clr) { m_label_color_locked=clr; }
//--- (1) Größe des Eingabefelder, (2) Ränder des Eingabefelder
void EditXSize(const int x_size) { m_edit_x_size=x_size; }
void EditXGap(const int x_gap) { m_edit_x_gap=x_gap; }
void EditYGap(const int y_gap) { m_edit_y_gap=y_gap; }
//--- (1) Art der Rücksetzung beim Drücken des Textfeldes, (2) Art der Textauswahl
bool ResetMode(void) { return(m_reset_mode); }
void ResetMode(const bool mode) { m_reset_mode=mode; }
};
Für das Zeitelement werden fünf private und eine public Methoden benötigt. Dieses zusammengesetzte Kontrollelement verwendet das fertige Steuerelement CSpinEdit als Eingabefeld.
{
private:
//--- Objekte zum Erstellen des Elementes
CRectLabel m_area;
CBmpLabel m_icon;
CLabel m_label;
CSpinEdit m_hours;
CSpinEdit m_minutes;
//---
public:
//--- Methoden zum Erstellen des Elementes
bool CreateTimeEdit(const long chart_id,const int subwin,const string label_text,const int x_gap,const int y_gap);
//---
private:
bool CreateArea(void);
bool CreateIcon(void);
bool CreateLabel(void);
bool CreateHoursEdit(void);
bool CreateMinutesEdit(void);
//---
public:
//--- Rückgabe des Pointers auf das Eingabefeld
CSpinEdit *GetHoursEditPointer(void) { return(::GetPointer(m_hours)); }
CSpinEdit *GetMinutesEditPointer(void) { return(::GetPointer(m_minutes)); }
};
Um automatisch die aktuellen Werte des Eingabefeldes abzufragen oder zu setzen (Stunden und Minuten) sollten die unten aufgeführten Methoden verwendet werden:
{
public:
//--- Abfragen und setzen der Wertes des Eingabefeldes
int GetHours(void) const { return((int)m_hours.GetValue()); }
int GetMinutes(void) const { return((int)m_minutes.GetValue()); }
void SetHours(const uint value) { m_hours.ChangeValue(value); }
void SetMinutes(const uint value) { m_minutes.ChangeValue(value); }
};
Ein Beispiel, wie diese Kontrollelement auf dem Chart des Terminals aussieht, findet sich weiter unten.
Die Listen von Kontrollkästchen
Das in einem der vorherigen Artikel beschriebene Kontrollelement List View (die Klasse CListView) wird für die Auswahl aus einer angebotenen Liste verwendet. Aber manchmal benötigt man die Auswahl mehrerer Elemente. Es könnte zum Beispiel notwendig sein, eine Liste von Symbolen oder Zeitrahmen zu erstellen, aus denen der Nutzer nur die für sein Handeln benötigten auswählt.
Die Liste der Komponenten zum Erstellen einer Liste von Kontrollkästchen:
- Gemeinsamer Hintergrund des Kontrollelements
- Vertikale Bildlaufleiste
- Gruppe des Kontrollkästchens:
- Hintergrund (Background)
- Symbol (Icon)
- Beschriftung
Fig. 2. Komponenten einer Liste von Kontrollkästchen
Als nächstes behandeln wir den Unterschied zwischen diesem Listentyp (CCheckBoxList) und einem einfachen (CListView), der bereits früher beschrieben wurde.
Die Klasse zum Erstellen der Listen von Kontrollkästchen
Erstellen Sie die Datei CheckBoxList.mqh mit der Klasse CCheckBoxList, die die Standardmethoden aller Kontrollelement der Bibliothek enthält. Als erste unterscheidet sie sich vom Listentyp CListView dadurch, dass sie aus drei grafischen Objekten besteht (siehe im Code weiter unten). Eine eigene private Methode wird für jeden Objekttyp erzeugt.
//| Die Klasse zum Erstellen der Listen von Kontrollkästchen |
//+------------------------------------------------------------------+
class CCheckBoxList : public CElement
{
private:
//--- Objekte zum Erstellen der Liste
CRectLabel m_area;
CEdit m_items[];
CBmpLabel m_checks[];
CLabel m_labels[];
CScrollV m_scrollv;
//---
public:
//--- Methoden zum Erstellen der Kontrollelemente
bool CreateCheckBoxList(const long chart_id,const int subwin,const int x_gap,const int y_gap);
//---
private:
bool CreateArea(void);
bool CreateList(void);
bool CreateItems(void);
bool CreateChecks(void);
bool CreateLabels(void);
bool CreateScrollV(void);
};
Zusätzlich zu den einzelnen Werten von jedem Element (Elementbeschreibung) wird ein Array für die Zustände der Kontrollkästchen (an/aus) benötigt. Die Methoden zur Abfrage und dem Setzen der Werte mit dem jeweiligen Index wird ebenfalls benötigt:
{
private:
//--- Array der Werte mit den Zuständen der Kontrollkästchen der Liste
string m_item_value[];
bool m_item_state[];
//---
public:
//--- Liefert/sichert (1) den Zustand und (2) Text des Listenelementes mit angegebenen Index
void SetItemState(const uint item_index,const bool state);
void SetItemValue(const uint item_index,const string value);
bool GetItemState(const uint item_index);
string GetItemValue(const uint item_index);
};
//+------------------------------------------------------------------+
//| Setzen des Zustands |
//+------------------------------------------------------------------+
void CCheckBoxList::SetItemState(const uint item_index,const bool state)
{
uint array_size=::ArraySize(m_item_state);
//--- Ist die Liste leer: Meldung
if(array_size<1)
::Print(__FUNCTION__," > This method is to be called, if the list has at least one item!");
//--- Anpassen, falls die Liste erweitert wurde
uint check_index=(item_index>=array_size)? array_size-1 : item_index;
//--- Sichern der Werte
m_item_state[check_index]=state;
//--- Bewegen der Liste gemäß der Bildlaufleiste
ShiftList();
}
//+------------------------------------------------------------------+
//| Abfrage des Zustandes der Liste der Kontrollkästchen |
//+------------------------------------------------------------------+
bool CCheckBoxList::GetItemState(const uint item_index)
{
uint array_size=::ArraySize(m_item_state);
//--- Gibt es kein Element in der Liste: Meldung
if(array_size<1)
::Print(__FUNCTION__," > This method is to be called, if the list has at least one item!");
//--- Anpassen, falls die Liste erweitert wurde
uint check_index=(item_index>=array_size)? array_size-1 : item_index;
//--- Sichern der Werte
return(m_item_state[check_index]);
}
Damit wurden die notwendigen Änderungen für die Handhabung der Liste gemacht. Sie können das jetzt selbst ausprobieren.
Das Sortieren von Tabellen
Wenn die grafische Schnittstelle einer Anwendung Datentabellen verwendet, könnte es hilfreich sein, diese nach einer nutzerbestimmten Spalte zu sortieren. Viele grafische Benutzeroberflächen sind so realisiert, dass eine Sortierung einfach durch einen Klick auf den Spaltenkopf ausgelöst wird. Der erste Klick auf den Spaltenkopf sortiert die Daten aufsteigend, also vom kleinsten zum größten Wert. Der zweite Klick sortiert dann absteigend, also vom größten zum kleinsten Wert.
Der Screenshot unten zeigt das Fenster der Toolbox des MetaEditor als dreispaltige Tabelle. Diese Tabelle wurde (absteigend) nach den Daten der dritten Spalte sortiert.
Fig. 3. Beispiel einer Tabelle mit sortierten Daten
In der Regel zeigt ein Pfeil im Spaltenkopf die Sortierungsrichtung. Zeigt er nach unten, wie im Bild oben, sind die Daten absteigend sortiert und vice versa.
Dieser Artikel behandelt nun das Sortieren von Tabellen des Typs CTable. Die Spaltenköpfe existiere ja bereits, sie müssen nur noch interaktiv gemacht werden. Zuerst sollen sie ihre Farbe ändern, falls sich über die Maus über ihr befindet und wenn sie geklickt wird. Dazu ergänzen wir die Klasse CTable mit Feldern und Methoden für die Farben der verschiedenen Zustände (siehe im Code unten).
{
private:
//--- Hintergrundfarbe des Spaltenkopfes
color m_headers_color;
color m_headers_color_hover;
color m_headers_color_pressed;
//---
public:
//--- Hintergrundfarben des Spaltenkopfes
void HeadersColor(const color clr) { m_headers_color=clr; }
void HeadersColorHover(const color clr) { m_headers_color_hover=clr; }
void HeadersColorPressed(const color clr) { m_headers_color_pressed=clr; }
};
Es liegt beim Nutzer, ob er eine sortierte Tabelle wünscht. Standardmäßig wird nicht sortiert. Für eine Sortierung verwenden Sie die Methode CTable::IsSortMode():
{
private:
//--- Sortiermodus der Daten der Spalten
bool m_is_sort_mode;
//---
public:
//--- Sortiermodus der Daten der Spalten
void IsSortMode(const bool flag) { m_is_sort_mode=flag; }
};
Die private Methode CTable::HeaderColorByHover() wechselt die Farbe des Spaltenkopfes, falls die Maus sich über ihr befindet. Sie wird durch den Event Handler des Kontrollelements aufgerufen.
{
private:
//--- Farbwechsel des Spaltenkopfes, falls die Maus sich darüber befindet
void HeaderColorByHover(void);
};
//+-------------------------------------------------------------------------+
//| Farbwechsel des Spaltenkopfes, falls die Maus sich darüber befindet |
//+-------------------------------------------------------------------------+
void CTable::HeaderColorByHover(void)
{
//--- Verlassen bei ausgeschalteter Sortierung
if(!m_is_sort_mode || !m_fix_first_row)
return;
//---
for(uint c=0; c<m_visible_columns_total; c++)
{
//--- Prüfe den Fokus des aktuellen Spaltenkopfes
bool condition=m_mouse.X()>m_columns[c].m_rows[0].X() && m_mouse.X()<m_columns[c].m_rows[0].X2() &&
m_mouse.Y()>m_columns[c].m_rows[0].Y() && m_mouse.Y()<m_columns[c].m_rows[0].Y2();
//---
if(!condition)
m_columns[c].m_rows[0].BackColor(m_headers_color);
else
{
if(!m_mouse.LeftButtonState())
m_columns[c].m_rows[0].BackColor(m_headers_color_hover);
else
m_columns[c].m_rows[0].BackColor(m_headers_color_pressed);
}
}
}
Um den Richtungspfeil für die sortierten Daten zu erstellen, muss die private Methode CTable::CreateSignSortedData() hinzugefügt werden. Ist vor dem Erstellen der Tabelle eine Sortierung nicht angeschaltet, wird kein Pfeil erzeugt. Ist eine Sortierung aber angeschaltet, wird, nach seiner Erzeugung, der Pfeil verborgen, da die Tabelle noch nicht sortiert wurde.
{
private:
//--- Objekte zum Erstellen einer Tabelle
CBmpLabel m_sort_arrow;
//---
private:
//--- Methoden zum Erstellen einer Tabelle
bool CreateSignSortedData(void);
};
//+------------------------------------------------------------------+
//| Erzeugt das Pfeil-Icon als Zeichen sortierte Daten |
//+------------------------------------------------------------------+
bool CTable::CreateSignSortedData(void)
{
//--- Verlassen, wenn eine Sortierung abgeschaltet ist
if(!m_is_sort_mode)
return(true);
//--- Bildung des Objektnamens
string name=CElement::ProgramName()+"_table_sort_array_"+(string)CElement::Id();
//--- Koordinaten
int x =(m_anchor_right_window_side)? m_columns[0].m_rows[0].X()-m_columns[0].m_rows[0].XSize()+m_sort_arrow_x_gap : m_columns[0].m_rows[0].X2()-m_sort_arrow_x_gap;
int y =(m_anchor_bottom_window_side)? CElement::Y()-m_sort_arrow_y_gap : CElement::Y()+m_sort_arrow_y_gap;
//--- Ist das Icon nicht festgelegt, verwende den Standardpfeil
if(m_sort_arrow_file_on=="")
m_sort_arrow_file_on="Images\\EasyAndFastGUI\\Controls\\SpinInc.bmp";
if(m_sort_arrow_file_off=="")
m_sort_arrow_file_off="Images\\EasyAndFastGUI\\Controls\\SpinDec.bmp";
//--- Erstellen des Objektes
if(!m_sort_arrow.Create(m_chart_id,name,m_subwin,x,y))
return(false);
//--- Bestimmen der Eigenschaften
m_sort_arrow.BmpFileOn("::"+m_sort_arrow_file_on);
m_sort_arrow.BmpFileOff("::"+m_sort_arrow_file_off);
m_sort_arrow.Corner(m_corner);
m_sort_arrow.GetInteger(OBJPROP_ANCHOR,m_anchor);
m_sort_arrow.Selectable(false);
m_sort_arrow.Z_Order(m_zorder);
m_sort_arrow.Tooltip("\n");
//--- Sichern der Koordinaten
m_sort_arrow.X(x);
m_sort_arrow.Y(y);
//--- Sichern der Größe (im Objekt)
m_sort_arrow.XSize(m_sort_arrow.X_Size());
m_sort_arrow.YSize(m_sort_arrow.Y_Size());
//--- Ränder der Kanten
m_sort_arrow.XGap((m_anchor_right_window_side)? x : x-m_wnd.X());
m_sort_arrow.YGap((m_anchor_bottom_window_side)? y : y-m_wnd.Y());
//--- Verbergen des Objektes
m_sort_arrow.Timeframes(OBJ_NO_PERIODS);
//--- Sichern des Pointers auf das Objekt
CElement::AddToArray(m_sort_arrow);
return(true);
}
Die Struktur der Werte und Eigenschaften der Tabellenzellen wird in einem Array mit der Anzahl der Dezimalstellen für jede Zelle, die reelle Zahlen enthalten, und durch ein Feld mit dem Datentyp jeder Spalte der Tabelle dargestellt.
{
private:
//--- Array der Werte und Eigenschaften der Tabelle
struct TOptions
{
ENUM_DATATYPE m_type;
string m_vrows[];
uint m_digits[];
ENUM_ALIGN_MODE m_text_align[];
color m_text_color[];
color m_cell_color[];
};
TOptions m_vcolumns[];
};
Wird ein Wert in eine Tabellenzelle eingetragen, wird die Anzahl der Dezimalstellen standardmäßig auf Null gesetzt:
{
public:
//--- Setze den Wert der angegebenen Tabellenzelle
void SetValue(const uint column_index,const uint row_index,const string value="",const uint digits=0);
};
Um den Datentyp der gefragten Tabellenspalte zu sichern oder abzufragen, verwenden wir die Methode CTable::DataType():
{
public:
//--- Abfrage/Sichern des Datentyps
ENUM_DATATYPE DataType(const uint column_index) { return(m_vcolumns[column_index].m_type); }
void DataType(const uint column_index,const ENUM_DATATYPE type) { m_vcolumns[column_index].m_type=type; }
};
Vor dem Erzeugen der Tabelle muss die Gesamtzahl der Spalten und Zeilen bekannt sein. Das Feld m_type und der Array m_digits werden mit Standardwerten initialisiert. Anfangs haben alle Spalten den Datentyp string (TYPE_STRING), und die Anzahl der Dezimalstellen ist Null.
//| Festlegen der Tabellengröße |
//+------------------------------------------------------------------+
void CTable::TableSize(const uint columns_total,const uint rows_total)
{
//--- Es muss mindesten 1 Spalte geben
m_columns_total=(columns_total<1) ? 1 : columns_total;
//--- Es muss mindesten zwei Zeilen geben
m_rows_total=(rows_total<2) ? 2 : rows_total;
//--- Festlegen der Spaltenzahl des Arrays
::ArrayResize(m_vcolumns,m_columns_total);
//--- Festlegen der Zeilenzahl des Arrays
for(uint i=0; i<m_columns_total; i++)
{
::ArrayResize(m_vcolumns[i].m_vrows,m_rows_total);
::ArrayResize(m_vcolumns[i].m_digits,m_rows_total);
::ArrayResize(m_vcolumns[i].m_text_align,m_rows_total);
::ArrayResize(m_vcolumns[i].m_text_color,m_rows_total);
::ArrayResize(m_vcolumns[i].m_cell_color,m_rows_total);
//--- Initialisiere den Array der Hintergrundfarben mit den Standardwerten
m_vcolumns[i].m_type=TYPE_STRING;
::ArrayInitialize(m_vcolumns[i].m_digits,0);
::ArrayInitialize(m_vcolumns[i].m_text_align,m_align_mode);
::ArrayInitialize(m_vcolumns[i].m_cell_color,m_cell_color);
::ArrayInitialize(m_vcolumns[i].m_text_color,m_cell_text_color);
}
}
Mehrere private Methoden werden für das Sortieren der Tabelle benötigt, dazu werden folgende Schritte unternommen:
- Der Sortieralgorithmus.
- Wertevergleich nach der benötigten Bedingung.
- Vertauschen der Werte in der Tabelle.
Mit der Methode CTable::Swap() werden die Werte in der Tabelle vertauscht. Hier werden die Zeilen der Tabelle vertauscht. Nicht nur die Wert der Zellen werden getauscht, sondern auch die Textfarbe.
{
private:
//--- Tausch der Werte der gegebenen Zellen
void Swap(uint c,uint r1,uint r2);
};
//+------------------------------------------------------------------+
//| Elementetausch |
//+------------------------------------------------------------------+
void CTable::Swap(uint c,uint r1,uint r2)
{
//--- Iteriere über alle Spalten in einer Schleife
for(uint i=0; i<m_columns_total; i++)
{
//--- Vertauschen des Textes
string temp_text =m_vcolumns[i].m_vrows[r1];
m_vcolumns[i].m_vrows[r1] =m_vcolumns[i].m_vrows[r2];
m_vcolumns[i].m_vrows[r2] =temp_text;
//--- Vertauschen der Textfarbe
color temp_text_color =m_vcolumns[i].m_text_color[r1];
m_vcolumns[i].m_text_color[r1] =m_vcolumns[i].m_text_color[r2];
m_vcolumns[i].m_text_color[r2] =temp_text_color;
}
}
Damit korrekt sortiert wird, werden die Werte des angegebenen Typs m_type verglichen. Dafür wurde eine eigene Methode CTable::CheckSortCondition() erstellt.
{
private:
//--- Prüfung der Sortierbedingung
bool CheckSortCondition(uint column_index,uint row_index,const string check_value,const bool direction);
};
//+------------------------------------------------------------------+
//| Wertevergleich nach den vorgegeben Sortierbedingungen |
//+------------------------------------------------------------------+
//| Richtung: true (>), false (<) |
//+------------------------------------------------------------------+
bool CTable::CheckSortCondition(uint column_index,uint row_index,const string check_value,const bool direction)
{
bool condition=false;
//---
switch(m_vcolumns[column_index].m_type)
{
case TYPE_STRING :
{
string v1=m_vcolumns[column_index].m_vrows[row_index];
string v2=check_value;
condition=(direction)? v1>v2 : v1<v2;
break;
}
//---
case TYPE_DOUBLE :
{
double v1=double(m_vcolumns[column_index].m_vrows[row_index]);
double v2=double(check_value);
condition=(direction)? v1>v2 : v1<v2;
break;
}
//---
case TYPE_DATETIME :
{
datetime v1=::StringToTime(m_vcolumns[column_index].m_vrows[row_index]);
datetime v2=::StringToTime(check_value);
condition=(direction)? v1>v2 : v1<v2;
break;
}
//---
default :
{
long v1=(long)m_vcolumns[column_index].m_vrows[row_index];
long v2=(long)check_value;
condition=(direction)? v1>v2 : v1<v2;
break;
}
}
//---
return(condition);
}
Die Methoden CTable::Swap() und CTable::CheckSortCondition() werden vom Sortieralgorithmus verwendet. Schauen wir einmal, welchen Sortieralgorithmus wir auswählen sollten. Zehn Algorithmen wurden getestet, darunter auch der Standardalgorithmus von MQL, die Funktion ArraySort():
- MQL-Sortierung
- Selectionsort
- Bubblesort
- Shakersort
- Insertionsort
- Shellsort
- Binary Tree Sort
- Quicksort
- Heapsort
- Mergesort
Als Test dieser Methoden muss ein Array von 100.000 (einhunderttausend) Elemente sortiert werden. Dieser Array wurde mit Zufallszahlen initialisiert. Die Testergebnisse zeigt das Histogramm unten:
Fig. 4. Diagramm der Testergebnisse der Sortiermethoden. Arraygröße beträgt 100.000 Elemente.
Die Quicksort-Methode ist nachweislich die Schnellste. Der Code dieser Methode stammt aus der Standardbibliothek – die Methode QuickSort(). In diesem Test ist er sogar besser als die Funktion ArraySort(). Die Sortierdauer wurde der höheren Genauigkeit wegen mit der Funktion GetMicrosecondCount() gemessen. Betrachten wir im Weiteren nur die mit den schnellten Ergebnissen (weniger als 1 Sekunde).
Fig. 5. Diagramm der schnellsten Testergebnisse einen Array mit 100.000 zu sortierenden Elementen.
Vergrößern wir den Array um den Faktor zehn, er umfasst jetzt 1.000.000 (eine Million) zu sortierenden Elementen.
Fig. 6. Diagramm der schnellsten Testergebnisse einen Array mit 1.000.000 zu sortierenden Elementen.
Der Standardalgorithmus, die Funktion ArraySort() erzielte das beste Ergebnis. Quicksort ist nur geringfügig langsamer und wird aus diesem Grund ausgewählt. ArraySort() eignet sich nicht für unsere Aufgabe, da: (1) in beiden Richtungen sortiert werden muss, (2) die Klasse CTable eine Array von Strukturen verwendet und (3) nicht nur der Ort der Tabellenzelle bestimmbar sein muss, sondern auch die ihre Eigenschaft.
Die Enumeration ENUM_SORT_MODE muss der Datei Enums.mqh für ein beiderseitiges Sortieren hinzugefügt werden:
- SORT_ASCEND – aufsteigend.
- SORT_DESCEND – absteigend.
//| Enumeration der Sortiermodi |
//+------------------------------------------------------------------+
enum ENUM_SORT_MODE
{
SORT_ASCEND =0,
SORT_DESCEND =1
};
Die aktuelle Version des Quicksort, die von der Methode CTable::QuickSort() verwendet wird, ist im Code unten zu sehen. Die Methoden CTable::Swap() und CTable::CheckSortCondition(), weiter oben im diesem Artikel besprochen, sind gelb hervorgehoben.
{
private:
//--- Quicksort Methode
void QuickSort(uint beg,uint end,uint column,const ENUM_SORT_MODE mode=SORT_ASCEND);
};
//+------------------------------------------------------------------+
//| Quicksort Algorithmus |
//+------------------------------------------------------------------+
void CTable::QuickSort(uint beg,uint end,uint column,const ENUM_SORT_MODE mode=SORT_ASCEND)
{
uint r1 =beg;
uint r2 =end;
uint c =column;
string temp =NULL;
string value =NULL;
uint data_total =m_rows_total-1;
//--- Durchlauf der Schleife, so lange der linke Index kleiner ist als der am weitesten rechts liegende
while(r1<end)
{
//--- Wähle den Wert in der Mitte der Reihe
value=m_vcolumns[c].m_vrows[(beg+end)>>1];
//--- Schleifendurchlauf, solange der linke Index kleiner als der gefundene rechte
while(r1<r2)
{
//--- Verschiebe den Index nach rechts bis die angegebene Bedingung erfüllt ist
while(CheckSortCondition(c,r1,value,(mode==SORT_ASCEND)? false : true))
{
//--- Prüfung der Arraygrenze
if(r1==data_total)
break;
r1++;
}
//--- Verschiebe den Index nach links bis die angegebene Bedingung erfüllt ist
while(CheckSortCondition(c,r2,value,(mode==SORT_ASCEND)? true : false))
{
//--- Prüfung der Arraygrenze
if(r2==0)
break;
r2--;
}
//--- Wenn der linke Index noch immer nicht größer als der rechte ist
if(r1<=r2)
{
//--- Vertausche die Werte
Swap(c,r1,r2);
//--- Falls das linke Ende erreicht wurde
if(r2==0)
{
r1++;
break;
}
//---
r1++;
r2--;
}
}
//--- Rekursive Fortführung des Algorithmus, bis der Anfang des Bereiches erreicht ist
if(beg<r2)
QuickSort(beg,r2,c,mode);
//--- Verengen des Bereiches für die nächste Iteration
beg=r1;
r2=end;
}
}
Alle diese Methoden sind privat. Betrachten wir jetzt die public Methode CTable::SortData(), die (1) durch einen Klick auf dem Spaltenkopf oder (2) automatisch immer dann, wenn es die Anwendung erfordert, aufgerufen werden soll. CTable::SortData() muss der Spaltenindex übergeben werden, standardmäßig ist das die erste Spalte. Wenn das erste Mal nach der übergebenen Spalte oder zuletzt absteigen sortiert wurde, werden die Werte aufsteigen sortiert. Nach dem Sortieren der Daten wird die Tabelle aktualisiert. Und die letzte Codezeile der Methode CTable::SortData() setzt den Pfeil gemäß der Sortierrichtung.
{
private:
//--- Index der zu sortierenden Spalte (WRONG_VALUE – Tabelle ist unsortiert)
int m_is_sorted_column_index;
//--- Letzte Sortierrichtung
ENUM_SORT_MODE m_last_sort_direction;
//---
public:
//--- Sortiere die Daten gemäß des übergebenen Spaltenindex
void SortData(const uint column_index=0);
};
//+------------------------------------------------------------------+
//| Sortiere die Daten gemäß des übergebenen Spaltenindex |
//+------------------------------------------------------------------+
void CTable::SortData(const uint column_index=0)
{
//--- Anfangsindex (unter Berücksichtigung der Kopfzeile) der Sortierung
uint first_index=(m_fix_first_row) ? 1 : 0;
//--- Der letzte Index des Arrays
uint last_index=m_rows_total-1;
//--- Die erste Sortierung erfolgt aufsteigend, jede folgende dann in der entgegengesetzten Richtung
if(m_is_sorted_column_index==WRONG_VALUE || column_index!=m_is_sorted_column_index || m_last_sort_direction==SORT_DESCEND)
m_last_sort_direction=SORT_ASCEND;
else
m_last_sort_direction=SORT_DESCEND;
//--- Sichern des Spaltenindex der Sortierung
m_is_sorted_column_index=(int)column_index;
//--- Sortieren
QuickSort(first_index,last_index,column_index,m_last_sort_direction);
//--- Tabellenaktualisierung
UpdateTable();
//--- Setzen des Pfeil entsprechend der Sortierrichtung
m_sort_arrow.State((m_last_sort_direction==SORT_ASCEND)? true : false);
}
Jetzt wird noch eine Methode zum Abfangen des Klicks auf den Spaltenkopf benötigt — CTable::OnClickTableHeaders(), falls der Sortiermodus aktiviert wurde. Nachdem alle Prüfungen dieses Kontrollelement bestanden wurden, wird der Spaltenkopf in einer Schleife bestimmt und dann wird die Tabelle sortiert. Unmittelbar nach der Sortierung der Tabelle wird ein Ereignis mit der neuen Kennung ON_SORT_DATA erzeugt. Zusätzlich zu dieser Ereigniskennung, enthält die Nachricht (1) die Kontrollelementskennung, (2) den Index der Sortierspalte und (3) den Datentyp dieser Spalte.
{
private:
//--- Behandlung des Klicks auf die Kopfzeile der Tabell
bool OnClickTableHeaders(const string clicked_object);
};
//+------------------------------------------------------------------+
//| Behandlung des Klicks auf dem Tabellenköpfen |
//+------------------------------------------------------------------+
bool CTable::OnClickTableHeaders(const string clicked_object)
{
//--- Verlassen bei ausgeschaltetem Sortiermodus
if(!m_is_sort_mode)
return(false);
//--- Verlassen beim Klick auf etwas anderes als einer Tabellenzelle
if(::StringFind(clicked_object,CElement::ProgramName()+"_table_edit_",0)<0)
return(false);
//--- Abfrage der Kennung des Objektnamens
int id=CElement::IdFromObjectName(clicked_object);
//--- Verlassen, wenn die Kennung nicht passt
if(id!=CElement::Id())
return(false);
//--- Verlassen, wenn es nicht der Tabellenkopf ist
if(RowIndexFromObjectName(clicked_object)>0)
return(false);
//--- Zur Bestimmung des Spaltenindex
uint column_index=0;
//--- Nächster Index, wenn der Kopfzeilenmodus fix ist
int l=(m_fix_first_column) ? 1 : 0;
//--- Abfrage der aktuellen Position der horizontalen Bildlaufleiste
int h=m_scrollh.CurrentPos()+l;
//--- Spalten
for(uint c=l; c<m_visible_columns_total; c++)
{
//--- Falls der Klick über einer Zelle erfolgte
if(m_columns[c].m_rows[0].Name()==clicked_object)
{
//--- Abfrage des Spaltenindex
column_index=(m_fix_first_column && c==0) ? 0 : h;
break;
}
//---
h++;
}
//--- Sortiere die Daten nach der angebenden Spalte
SortData(column_index);
//--- Sende darüber eine Nachricht
::EventChartCustom(m_chart_id,ON_SORT_DATA,CElement::Id(),m_is_sorted_column_index,::EnumToString(DataType(column_index)));
return(true);
}
Ist die Gesamtzahl der Spalten größer als die Zahl der sichtbaren, muss die Position des Richtungspfeils der sortierten Tabelle beim Verschieben der waagerechten Bildlaufleiste angepasst werden. Das wird von der privaten Methode CTable::ShiftSortArrow() erledigt.
{
private:
//--- Verschieben des Pfeils der Sortierrichtung
void ShiftSortArrow(const uint column);
};
//+------------------------------------------------------------------+
//| Verschieben des Pfeils auf die Tabellenspalte der Sortierung |
//+------------------------------------------------------------------+
void CTable::ShiftSortArrow(const uint column)
{
//--- Zeige das Objekt, wenn es nicht verborgen ist
if(CElement::IsVisible())
m_sort_arrow.Timeframes(OBJ_ALL_PERIODS);
//--- Berechne und setze die Koordinaten
int x=m_columns[column].m_rows[0].X2()-m_sort_arrow_x_gap;
m_sort_arrow.X(x);
m_sort_arrow.X_Distance(x);
//--- Die Ränder der Kanten
m_sort_arrow.XGap((m_anchor_right_window_side)? m_wnd.X2()-x : x-m_wnd.X());
}
Diese Methode wird aus der Methode CTable::UpdateTable() aufgerufen, in dem Codabschnitt, in dem die Spaltenköpfe verändert werden. Unten ist eine verkürzte Version der Methode CTable::UpdateTable() mit den ergänzten Teilen. Hier, wenn eine sortierte Spalte beim ersten Lauf gefunden wurde, wird das Flag gesetzt und der Richtungspfeil verschoben. Nachdem der Durchlauf beendet ist, könnte es passieren, dass zwar eine sortierte Spalte existiert, sie aber nicht im vorherigen Durchlauf gefunden wurde. Das bedeutet, dass sie außerhalb des sichtbaren Bereiches ist und zu verbergen ist. Falls das die erste Spalte (Index Null) ist und sie gleichzeitig fixiert ist und nicht bewegt werden kann, wird der Pfeil dort gesetzt.
//| Aktualisiere die Tabellendaten unter Berücksichtigung der letzten Änderungen |
//+------------------------------------------------------------------+
void CTable::UpdateTable(void)
{
//...
//--- Verschieben der Kopfzeile in die oberste Zeile
if(m_fix_first_row)
{
//--- Für die Bestimmung des Verschubs des Richtungspfeils
bool is_shift_sort_arrow=false;
//--- Spalten
for(uint c=l; c<m_visible_columns_total; c++)
{
//--- Falls innerhalb der Arraygröße
if(h>=l && h<m_columns_total)
{
//--- Wenn die Sortierspalte gefunden wurde
if(!is_shift_sort_arrow && m_is_sort_mode && h==m_is_sorted_column_index)
{
is_shift_sort_arrow=true;
//--- Anpassen des Richtungspfeils
uint column=h-(h-c);
if(column>=l && column<m_visible_columns_total)
ShiftSortArrow(column);
}
//--- Anpassen der (1) Werte, (2) Hintergrundfarbe, (3) Textfarbe und (4) Textausrichtung der Zelle
SetCellParameters(c,0,m_vcolumns[h].m_vrows[0],m_headers_color,m_headers_text_color,m_vcolumns[h].m_text_align[0]);
}
//---
h++;
}
//--- Falls die Tabelle sortiert ist, aber der Spaltenindex nicht gefunden wurde
if(!is_shift_sort_arrow && m_is_sort_mode && m_is_sorted_column_index!=WRONG_VALUE)
{
//--- Verbergen, wenn der Index größer als Null ist
if(m_is_sorted_column_index>0 || !m_fix_first_column)
m_sort_arrow.Timeframes(OBJ_NO_PERIODS);
//--- Setzen der Kopfzeile der ersten Spalte
else
ShiftSortArrow(0);
}
}
//...
}
Die Dateien mit den Test-Experten können am Ende des Artikels von da heruntergeladen werden. Verwenden Sie sie, um den gesamten Vorgang selbst zu testen.
Weitere Aktualisierungen der Bibliothek
Als Ergänzung existieren folgende Aktualisierungen der Bibliothek:
1. Es ist nun möglich, Schrifttyp und -größe für jedes Kontrollelement individuell festzulegen. Dafür wurden die entsprechenden Methoden und Felder der Basisklasse CElement der Kontrollelement hinzugefügt. Standardmäßig wird der Schrifttyp Calibri in der Größe 8 Punkt verwendet.
{
protected:
//--- Schrifttyp
string m_font;
int m_font_size;
//---
public:
//--- (1) Typ und (2) Größe
void Font(const string font) { m_font=font; }
string Font(void) const { return(m_font); }
void FontSize(const int font_size) { m_font_size=font_size; }
int FontSize(void) const { return(m_font_size); }
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CElement::CElement(void) : m_font("Calibri"),
m_font_size(8)
{
}
Entsprechend werden die Werte der Basisklasse aller Methoden zum Erstellen der Kontrollelemente verwendet, die auch die Bestimmung der Schrift umfassen. Unten ist ein Beispiel für ein Textfeld der Klasse CCheckBox. Das Gleiche wurde für alle Klassen der Bibliothek gemacht..
//| Erstellen des Textfeldes der Kontrollkästchen |
//+------------------------------------------------------------------+
bool CCheckBox::CreateLabel(void)
{
//--- Bilden des Objektnamens
string name=CElement::ProgramName()+"_checkbox_lable_"+(string)CElement::Id();
//--- Koordinaten
int x =(m_anchor_right_window_side)? m_x-m_label_x_gap : m_x+m_label_x_gap;
int y =(m_anchor_bottom_window_side)? m_y-m_label_y_gap : m_y+m_label_y_gap;
//--- Textfarbe gemäß des Zustandes
color label_color=(m_check_button_state) ? m_label_color : m_label_color_off;
//--- Objekt erstellen
if(!m_label.Create(m_chart_id,name,m_subwin,x,y))
return(false);
//--- Festlegen der Eigenschaften
m_label.Description(m_label_text);
m_label.Font(CElement::Font());
m_label.FontSize(CElement::FontSize());
m_label.Color(label_color);
m_label.Corner(m_corner);
m_label.Anchor(m_anchor);
m_label.Selectable(false);
m_label.Z_Order(m_zorder);
m_label.Tooltip("\n");
//--- Ränder der Kanten
m_label.XGap((m_anchor_right_window_side)? x : x-m_wnd.X());
m_label.YGap((m_anchor_bottom_window_side)? y : y-m_wnd.Y());
//--- Initialisierung des Gradientenarrays
CElement::InitColorArray(label_color,m_label_color_hover,m_label_color_array);
//--- Sichern des Pointers auf das Objekt
CElement::AddToArray(m_label);
return(true);
}
2. Die Ränder der Kanten jedes Kontrollelementes der grafische Benutzeroberfläche müssen jetzt direkt der Methode zum Erstellen des Kontrollelementes übergeben werden. Die Berechnung geschieht automatisch. Als Beispiel zeigt der Code unten die Methode, um einen Drop-Down-Kalender aus der Nutzerklasse CProgram zu erstellen.
//| Erstellen eines Drop-Down-Kalenders |
//+------------------------------------------------------------------+
bool CProgram::CreateDropCalendar(const int x_gap,const int y_gap,const string text)
{
//--- Übergabe des Objektes an das Panel
m_drop_calendar.WindowPointer(m_window);
//--- Hinzufügen des zweiten Teils
m_tabs.AddToElementsArray(1,m_drop_calendar);
//--- Setze die Eigenschaften vor dem Erstellen
m_drop_calendar.XSize(140);
m_drop_calendar.YSize(20);
m_drop_calendar.AreaBackColor(clrWhite);
//--- Erstellen des Kontrollelementes
if(!m_drop_calendar.CreateDropCalendar(m_chart_id,m_subwin,text,x_gap,y_gap))
return(false);
//--- Übergabe des Pointers des Kontrollelementes an die Basis
CWndContainer::AddToElementsArray(0,m_drop_calendar);
return(true);
}
Anwendung zum Testen der Kontrollelemente
Jetzt erstellen wir eine MQL-Testprogramm, um alle neuen Kontrollelemente auszuprobieren. Wir erstellen eine grafische Benutzeroberfläche mit einem Hauptmenü (CMenuBar), einem Drop-Down-Kontextmenü, einer Statusbar und zwei Karteireiter Der erste Karteireiter enthält eine Tabelle vom Typ CTable mit aktivierter Sortierung.
Die drei Spalten der Tabelle haben haben folgende Datentypen:
- Die erste Spalte - TYPE_DATETIME.
- Die zweite Spalte - TYPE_DOUBLE.
- Die dritte Spalte - TYPE_LONG.
Die verbleibenden Spalten sind vom Standarddatentyp TYPE_STRING. Der Screenshot unten zeigt das Aussehen der grafische Benutzeroberfläche mit der Tabelle des ersten Karteireiters.
Fig. 7. Beispiel der nach der zweiten Spalte aufsteigend sortierten Tabelle.
Erstelle vier Kontrollelemente im zweiten Karteireiter:
- Drop-Down-Kalender (die Klasse CDropCalendar).
- Kontrollelement für Zeit (die Klasse CTimeEdit).
- Liste von Kontrollkästchen (die Klasse CCheckBoxList).
- Listenansicht (die Klasse CListView).
Der Screenshot unten zeigt das Aussehen der grafische Benutzeroberfläche des MQL Testprogramms:
Fig. 8. Kontrollelemente des zweiten Karteireiters.
Der Quellcode dieses Testprogramms findet sich am Ende dieses Artikels.
Schlussfolgerung
Zur Zeit schaut das Schema der Bibliothek zum Erstellen einer grafische Benutzeroberfläche wie folgt aus:
Fig. 9. Struktur der Bibliothek im augenblicklichen Entwicklungszustand.
Dies ist nicht der letzte Artikel zur Serie grafische Benutzeroberflächen. Wir werden fortfahren, sie zu verbessern und neue Eigenschaften hinzuzufügen. Unten könne Sie die letzten Versionen der Bibliothek und der Dateien zum Testen herunterladen.
Bei Fragen zur Verwendung des bereitgestellten Materials, können Sie auf die detaillierten Beschreibungen im Lauf der Entwicklung der Bibliothek in den vorangegangenen Artikeln dieser Serie zurückgreifen oder sie stellen Ihre Fragen im Kommentarteil diese Artikels.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/2897





- 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.