MetaTrader 5 herunterladen

Grafische Interfaces X: Elemente der Zeit, Listen von Kontrollkästchen und das Sortieren von Tabellen (build 6)

13 Februar 2017, 09:27
Anatoli Kazharski
0
176

Inhalt


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.

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. 

class CTimeEdit : public CElement
  {
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: 

class CTimeEdit : public CElement
  {
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:

  1. Gemeinsamer Hintergrund des Kontrollelements
  2. Vertikale Bildlaufleiste
  3. Gruppe des Kontrollkästchens:
    • Hintergrund (Background)
    • Symbol (Icon)
    • Beschriftung

Fig. 2. Komponenten einer Liste von Kontrollkästchen

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: 

class CCheckBoxList : public CElement
  {
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

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

class CTable : public CElement
  {
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(): 

class CTable : public CElement
  {
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. 

class CTable : public CElement
  {
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. 

class CTable : public CElement
  {
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.

class CTable : public CElement
  {
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: 

class CTable : public CElement
  {
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():

class CTable : public CElement
  {
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. 

class CTable : public CElement
  {
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.

class CTable : public CElement
  {
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.

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.

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.

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. 

class CTable : public CElement
  {
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. 

class CTable : public CElement
  {
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.

class CTable : public CElement
  {
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.

class CTable : public CElement
  {
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. 

class CElement
  {
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 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.

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.

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.

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 Software Corp.
Originalartikel: https://www.mql5.com/ru/articles/2897

Beigefügte Dateien |
3D-Modellierung in MQL5 3D-Modellierung in MQL5

Eine Zeitreihe stellt ein dynamisches System dar, in welches Werte einer zufälligen Größe einer nach dem anderen eintreffen: kontinuierlich oder in gewissen Zeitabständen. Der Übergang von einer flachen zur dreidimensionalen Analyse des Marktes ermöglicht es, komplexe Prozesse und Erscheinungen aus einer neuen Perspektive zu betrachten. In diesem Artikel werden Visualisierungsfunktionen für eine 3D-Darstellung zweidimensionaler Daten beschrieben.

Automatische Ermittlung von Extremwerten basierend auf einem angegebenen Kursrückgang Automatische Ermittlung von Extremwerten basierend auf einem angegebenen Kursrückgang

Bei der Automatisierung von Handelsstrategien, die grafische Muster verwenden, ist es notwendig, Extremwerte auf den Charts für eine weitere Verarbeitung und Interpretation zu ermitteln. Bestehende Tools bieten nicht immer diese Möglichkeit. Die im Artikel beschriebenen Algorithmen erlauben es, alle Extremwerte auf den Charts zu ermitteln. Die entwickelten Tools sind effektiv sowohl in einem Trend, als auch in einem Seitwärtsmarkt. Die erhaltenen Ergebnisse hängen von der gewählten Timeframe ab und werden nur durch den angegebenen Rückgang definiert.

Grafische Interfaces X: Erweitertes Management von Listen und Tabellen Code Optimierung (build 7) Grafische Interfaces X: Erweitertes Management von Listen und Tabellen Code Optimierung (build 7)

Der Code der Bibliothek muss optimiert werden: Er sollte besser dem Standard folgen, das heißt — leichter lesbar und schneller zu verstehen. Weiters werden wir die als letztes entwickelten Kontrollelemente weiterentwickeln: Listen, Tabellen und Bildlaufleisten.

Integrieren Sie das MetaTrader 4/5 Webterminal in Ihre Webseite - das ist kostenlos, und man kann damit Geld verdienen Integrieren Sie das MetaTrader 4/5 Webterminal in Ihre Webseite - das ist kostenlos, und man kann damit Geld verdienen

Trader sind mit dem Webterminal gut vertraut, in dem man auf Finanzmärkten direkt im Browser handeln kann. Wir schlagen Ihnen vor, das Webterminal auf Ihrer Webseite absolut kostenlos zu platzieren. Sie haben Besucher, Broker sind an neuen Leads interessiert, und wir bieten eine fertige Weblösung an. Damit alles funktioniert, brauchen Sir nur einen iframe in Ihre Webseite zu integrieren.