English Русский 中文 Español 日本語 Português
Grafisches Interface X: Sortieren, Neuerstellen der Tabelle und Steuerelemente der Zellen (build 11)

Grafisches Interface X: Sortieren, Neuerstellen der Tabelle und Steuerelemente der Zellen (build 11)

MetaTrader 5Beispiele | 2 Mai 2017, 09:58
478 0
Anatoli Kazharski
Anatoli Kazharski

Inhalt

‌‌

Einführung

Der erste Artikel Grafische Interfaces I: Vorbereitung der Bibliotheksstruktur (Kapitel 1) beschreibt im Detail den Zweck der Bibliothek. 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 Tabellendarstellung wird weiterentwickelt. Zählen wir ihre neuen Eigenschaften auf.

  • Sortieren der Daten der Tabelle.
  • Verwalten der Anzahl der Spalten und Zeilen: Hinzufügen und entfernen von Spalten und Zeilen zum angegebenen Index, vollständiges Löschen der Tabelle (es bleibt nur eine Spalte und eine Zeile); Neuerstellung der Tabelle (löscht die Tabelle und setzt neue Dimensionen).
  • Erweiterung der Möglichkeiten des Nutzers des grafischen Interfaces: Der Doppelklick mit der linken Maustaste wurde hinzugefügt.
  • Wir beginnen Steuerelementen, die Tabellenzellen zugewiesen werden können: Kontrollkästchen und Tasten.

Wenn Sie bereits ein grafisches Interface mit der Hilfe dieser Bibliothek erstellt haben und eine Tabelle des Typs CTable zur Anzeige von Daten verwenden, wird Ihnen empfohlen im Weiteren Tabellen des Typs CCanvasTable zu verwenden. Beginnend mit diesem Artikel sind sie ganz auf Augenhöhe mit Tabellen anderen Typs dieser Bibliothek und ja übertreffen sie in mancher Hinsicht.

Das Sortieren von Tabellen

Die meisten Methoden für die Sortierung der Tabellendaten sind die gleichen wie in CTable. Der Artikel Grafische Interfaces I: Vorbereitung der Bibliotheksstruktur (Kapitel 1) beschreibt, wie diese im Detail funktionieren. Hier werden die Änderungen und Ergänzungen bezüglich der Tabelle des Typs CCanvasTable nur kurz erwähnt.

Das Zeichnen der Icons zur Kennzeichnung sortierter Tabelle verlangt einen static Array vom Typ CTImage mit zwei Elementen. Es enthält die Pfade zu den Icons für die Anzeige der Sortierrichtung. Wenn der Nutzer keine eigene Icons benannt hat, werden standardmäßige Icons verwendet. Die Initialisierung mit Standardwerten und das Ausfüllen des Array mit den Icons übernimmt die Methode CCanvasTable::CreateHeaders() für das Erstellen der Kopfzeilen. 

//+------------------------------------------------------------------+
//| Kasse des Erzeugens eine Tabellendarstellung                     |
//+------------------------------------------------------------------+
class CCanvasTable : public CElement
  {
private:
   //--- Icons als Zeichen der sortierten Daten
   CTImage           m_sort_arrows[2];
   //---
public:
   //--- Eintragen der Icons als Zeichen der sortierten Daten
   void              SortArrowFileAscend(const string path)  { m_sort_arrows[0].m_bmp_path=path; }
   void              SortArrowFileDescend(const string path) { m_sort_arrows[1].m_bmp_path=path; }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCanvasTable::CCanvasTable(void) 
  {
...
//--- Initialisierung der Struktur der Sortiericons
   m_sort_arrows[0].m_bmp_path="";
   m_sort_arrows[1].m_bmp_path="";
  }
//+------------------------------------------------------------------+
//| Erstellen der Spaltenköpfe                                       |
//+------------------------------------------------------------------+
bool CCanvasTable::CreateHeaders(void)
  {
//--- Verlassen, wenn die Spaltenköpfe deaktiviert sind
   if(!m_show_headers)
      return(true);
//--- Bilden des Objektnamens
   string name=CElementBase::ProgramName()+"_table_headers_"+(string)CElementBase::Id();
//--- Koordinaten
   int x =m_x+1;
   int y =m_y+1;
//--- Definieren der Icons als Zeichen der Sortierfähigkeit der Tabelle
   if(m_sort_arrows[0].m_bmp_path=="")
      m_sort_arrows[0].m_bmp_path="::Images\\EasyAndFastGUI\\Controls\\SpinInc.bmp";
   if(m_sort_arrows[1].m_bmp_path=="")
      m_sort_arrows[1].m_bmp_path="::Images\\EasyAndFastGUI\\Controls\\SpinDec.bmp";
//---
   for(int i=0; i<2; i++)
     {
      ::ResetLastError();
      if(!::ResourceReadImage(m_sort_arrows[i].m_bmp_path,m_sort_arrows[i].m_image_data,
         m_sort_arrows[i].m_image_width,m_sort_arrows[i].m_image_height))
        {
         ::Print(__FUNCTION__," > error: ",::GetLastError());
        }
     }
//--- Erstellen des Objektes
   ::ResetLastError();
   if(!m_headers.CreateBitmapLabel(m_chart_id,m_subwin,name,x,y,m_table_x_size,m_header_y_size,COLOR_FORMAT_ARGB_NORMALIZE))
     {
      ::Print(__FUNCTION__," > Failed to create a canvas for drawing the table headers: ",::GetLastError());
      return(false);
     }
//--- Starten auf dem Chart
//--- Festlegen der Eigenschaften
//--- Koordinaten
//--- Sichern der Größe
//--- Abstände vom Rand des Panels
//--- Sichern des Objektpointers
//--- Setzen der Größe des sichtbaren Teils
//--- Setzen des Rahmenabstandes im Bild für die X- und die Y-Achse
...
   return(true);
  }

Die Methode CCanvasTable::DrawSignSortedData() wird für das Zeichnen der Icons sortierter Arrays verwendet. Dies Element wird nur gezeichnet, wenn (1) Sortiermodus aktiviert ist und (2) das Sortieren der Tabelle wurde bereits durchgeführt. Die Abstände können mit den Methoden CCanvasTable::SortArrowXGap() und CCanvasTable::SortArrowYGap() gesteuert werden. Das Icon für eine aufsteigende Sortierung erhält den Index 0 0, eine absteigende – 1. Dann folgt die Doppelschleife zum Zeichnen des Icons. Dieses Thema wurde bereits detailliert besprochen.‌

class CCanvasTable : public CElement
  {
private:
   //--- Abstand des Icons der sortierten Daten
   int               m_sort_arrow_x_gap;
   int               m_sort_arrow_y_gap;
   //---
public:
   //--- Abstand des Icons der sortierten Tabelle
   void              SortArrowXGap(const int x_gap)          { m_sort_arrow_x_gap=x_gap;         }
   void              SortArrowYGap(const int y_gap)          { m_sort_arrow_y_gap=y_gap;         }
   //---
private:
   //--- Zeichen des Icons für die Möglichkeit der Sortierung der Tabelle
   void              DrawSignSortedData(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCanvasTable::CCanvasTable(void) : m_sort_arrow_x_gap(20),
                                   m_sort_arrow_y_gap(6)
  {
...
  }
//+------------------------------------------------------------------+
//| Zeichen des Icons für die Möglichkeit der Sortierung der Tabelle |
//+------------------------------------------------------------------+
void CCanvasTable::DrawSignSortedData(void)
  {
//--- Verlassen, wenn (1) Sortieren ist deaktiviert oder (2) noch nicht sortiert
   if(!m_is_sort_mode || m_is_sorted_column_index==WRONG_VALUE)
      return;
//--- Berechnen der Koordinaten
   int x =m_columns[m_is_sorted_column_index].m_x2-m_sort_arrow_x_gap;
   int y =m_sort_arrow_y_gap;
//--- Das ausgewählte Icon für die Sortierrichtung
   int image_index=(m_last_sort_direction==SORT_ASCEND)? 0 : 1;
//--- Zeichnen
   for(uint ly=0,i=0; ly<m_sort_arrows[image_index].m_image_height; ly++)
     {
      for(uint lx=0; lx<m_sort_arrows[image_index].m_image_width; lx++,i++)
        {
         //--- Gibt es keine Farbe, gehe zum nächsten Pixel
         if(m_sort_arrows[image_index].m_image_data[i]<1)
            continue;
         //--- Abrufen der Farbe des Hintergrundes (des Spaltenkopfes) und der Farbe des jeweiligen Pixels des Icons
         uint background  =m_headers.PixelGet(x+lx,y+ly);
         uint pixel_color =m_sort_arrows[image_index].m_image_data[i];
         //--- Mischen der Farben
         uint foreground=::ColorToARGB(m_clr.BlendColors(background,pixel_color));
         //--- Zeichnen der Pixel des darüber gelegten Icons
         m_headers.PixelSet(x+lx,y+ly,foreground);
        }
     }
  }

Die Methode CCanvasTable::Swap() tauscht die Werte in der Tabelle während des Sortierens. Der Unterschied ist nur, dass nicht nur der Text und/oder das Bild der Zelle bewegt werden muss, sondern auch eine große Anzahl von Zelleigenschaften. Der Einfachheit halber und um Duplikate zu entfernen, benötigen wir eine Hilfsmethode CCanvasTable::ImageCopy(), um die Bilder umzukopieren. Ihr werden zwei Arrays des Typs CTImage übergeben — der Empfänger und die Quelle der Daten. Der Index des kopierten Bildes wird als drittes Argument übergeben, da eine Zelle auch mehrere Bilder enthalten kann.

class CCanvasTable : public CElement
  {
private:
   //--- Kopieren der Bilddaten von einem Array zum anderen
   void              ImageCopy(CTImage &destination[],CTImage &source[],const int index);
  };
//+------------------------------------------------------------------+
//| Kopieren der Bilddaten von einem Array zum anderen               |
//+------------------------------------------------------------------+
void CCanvasTable::ImageCopy(CTImage &destination[],CTImage &source[],const int index)
  {
//--- Kopieren der Pixel des Bildes
   ::ArrayCopy(destination[index].m_image_data,source[index].m_image_data);
//--- Kopieren der Eigenschaften des Bildes
   destination[index].m_image_width  =source[index].m_image_width;
   destination[index].m_image_height =source[index].m_image_height;
   destination[index].m_bmp_path     =source[index].m_bmp_path;
  }

Unten ist der Code von CCanvasTable::Swap() aufgeführt. Vor dem Kopieren der Bilder muss zuerst überprüft werden, ob sie existieren, um, falls nicht, mit der nächsten Spalte fortzusetzen. Wenn eine der Zellen ein Bild enthält, dann werden sie mit der Methode CCanvasTable::ImageCopy() kopiert. Dieser Vorgang ist der gleiche wie beim Kopieren der anderen Eigenschaften der Zelle. Das bedeutet, wir sichern als erstes die Eigenschaften der ersten Zelle. Dann werden die Eigenschaften der zweiten Zelle in die erste Zelle kopiert. Schließlich werden die zuerst gesicherten Werte der ersten Zelle in die zweite Zelle kopiert.

class CCanvasTable : public CElement
  {
private:
   //--- Tauschen der Werte der angegebenen Zellen
   void              Swap(uint r1,uint r2);
  };
//+------------------------------------------------------------------+
//| Tauschen der Elemente                                            |
//+------------------------------------------------------------------+
void CCanvasTable::Swap(uint r1,uint r2)
  {
//--- Iterieren über alle Spalten in einer Schleife
   for(uint c=0; c<m_columns_total; c++)
     {
      //--- Tauschen des Volltextes
      string temp_text                    =m_columns[c].m_rows[r1].m_full_text;
      m_columns[c].m_rows[r1].m_full_text =m_columns[c].m_rows[r2].m_full_text;
      m_columns[c].m_rows[r2].m_full_text =temp_text;
      //--- Tauschen des verkürzten Textes
      temp_text                            =m_columns[c].m_rows[r1].m_short_text;
      m_columns[c].m_rows[r1].m_short_text =m_columns[c].m_rows[r2].m_short_text;
      m_columns[c].m_rows[r2].m_short_text =temp_text;
      //--- Tauschen der Dezimalstellen
      uint temp_digits                 =m_columns[c].m_rows[r1].m_digits;
      m_columns[c].m_rows[r1].m_digits =m_columns[c].m_rows[r2].m_digits;
      m_columns[c].m_rows[r2].m_digits =temp_digits;
      //--- Tauschen der Textfarbe
      color temp_text_color                =m_columns[c].m_rows[r1].m_text_color;
      m_columns[c].m_rows[r1].m_text_color =m_columns[c].m_rows[r2].m_text_color;
      m_columns[c].m_rows[r2].m_text_color =temp_text_color;
      //--- Tauschen des Index des gewählten Icons
      int temp_selected_image                  =m_columns[c].m_rows[r1].m_selected_image;
      m_columns[c].m_rows[r1].m_selected_image =m_columns[c].m_rows[r2].m_selected_image;
      m_columns[c].m_rows[r2].m_selected_image =temp_selected_image;
      //--- Prüfen, ob die Zelle eine Bild enthält
      int r1_images_total=::ArraySize(m_columns[c].m_rows[r1].m_images);
      int r2_images_total=::ArraySize(m_columns[c].m_rows[r2].m_images);
      //--- Gehen zur nächsten Spalte, wenn beide Zellen keine Bilder enthalten
      if(r1_images_total<1 && r2_images_total<1)
         continue;
      //--- Tauschen der Bilder
      CTImage r1_temp_images[];
      //---
      ::ArrayResize(r1_temp_images,r1_images_total);
      for(int i=0; i<r1_images_total; i++)
         ImageCopy(r1_temp_images,m_columns[c].m_rows[r1].m_images,i);
      //---
      ::ArrayResize(m_columns[c].m_rows[r1].m_images,r2_images_total);
      for(int i=0; i<r2_images_total; i++)
         ImageCopy(m_columns[c].m_rows[r1].m_images,m_columns[c].m_rows[r2].m_images,i);
      //---
      ::ArrayResize(m_columns[c].m_rows[r2].m_images,r1_images_total);
      for(int i=0; i<r1_images_total; i++)
         ImageCopy(m_columns[c].m_rows[r2].m_images,r1_temp_images,i);
     }
  }

Der Klick auf einer der Spaltenköpfe ruft die Methode CCanvasTable::OnClickHeaders() auf, die den Sortiervorgang startet. In dieser Klasse ist alles eindeutig einfacher als in der Tabelle des Typs CTable: Vergleichen und sehen Sie selbst.

class CCanvasTable : public CElement
  {
private:
   //--- Abfangen des Klicks auf einen Spaltenkopf
   bool              OnClickHeaders(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Abfangen des Klicks auf einen Spaltenkopf                        |
//+------------------------------------------------------------------+
bool CCanvasTable::OnClickHeaders(const string clicked_object)
  {
//--- Verlassen, wenn (1) der Sortiermodus deaktiviert ist oder (2) die Spaltenbreite gerade geändert wird
   if(!m_is_sort_mode || m_column_resize_control!=WRONG_VALUE)
      return(false);
//--- Verlassen, wenn die Bildlaufleiste aktiv ist
   if(m_scrollv.ScrollState() || m_scrollh.ScrollState())
      return(false);
//--- Verlassen, wenn es ein anderer Objektname ist
   if(m_headers.Name()!=clicked_object)
      return(false);
//--- Bestimmung des Spaltenindex
   uint column_index=0;
//--- Abruf der realtive X-Koordinate unter dem Mauskursor
   int x=m_mouse.RelativeX(m_headers);
//--- Bestimmen des geklickten Spaltenkopfes
   for(uint c=0; c<m_columns_total; c++)
     {
      //--- Ist der Spaltenkopf gefunden, sichern dessen Index
      if(x>=m_columns[c].m_x && x<=m_columns[c].m_x2)
        {
         column_index=c;
         break;
        }
     }
//--- Sortieren der Daten gemäß der angegebenen Spalte
   SortData(column_index);
//--- Senden einer Nachricht darüber
   ::EventChartCustom(m_chart_id,ON_SORT_DATA,CElementBase::Id(),m_is_sorted_column_index,::EnumToString(DataType(column_index)));
   return(true);
  }

Die anderen Methoden zum Sortieren der Daten bleiben unverändert und unterscheiden sich nicht von den früher besprochenen. Daher betrachten wir nur eine Liste dieser Variablen und Methoden der Klasse CCanvasTable:‌

class CCanvasTable : public CElement
  {
private:
   //--- Sortierrichtung der Daten in der Spalte
   bool              m_is_sort_mode;
   //--- Index der sortierten Spalte (WRONG_VALUE – Tabelle ist nicht sortiert)
   int               m_is_sorted_column_index;
   //--- Letzt Sortierrichtung
   ENUM_SORT_MODE    m_last_sort_direction;
   //---
public:
   //--- Sortiermodus der Daten
   void              IsSortMode(const bool flag)             { m_is_sort_mode=flag;              }

   //--- Abfrage/Sichern des Datentyps
   ENUM_DATATYPE     DataType(const uint column_index);
   void              DataType(const uint column_index,const ENUM_DATATYPE type);
   //--- Sortieren der Daten gemäß der angegebenen Spalte
   void              SortData(const uint column_index=0);
   //---
private:
   //--- Abfangen des Klicks auf einen Spaltenkopf
   bool              OnClickHeaders(const string clicked_object);

   //--- Quicksort Methode
   void              QuickSort(uint beg,uint end,uint column,const ENUM_SORT_MODE mode=SORT_ASCEND);
   //--- Prüfen der Sortierbedingungen
   bool              CheckSortCondition(uint column_index,uint row_index,const string check_value,const bool direction);
  };


Das Sortieren einer Tabelle diesen Typs ist unten gezeigt:

 Fig. 1. Demonstration des Sortierens einer Tabelle vom Typ CCanvasTable.‌

Fig. 1. Demonstration des Sortierens einer Tabelle vom Typ CCanvasTable.‌


Hinzufügen und entfernen von Spalten und Zeilen

In einen früheren Artikel wurden die Methoden entwickelt, um Spalten und Zeilen einer Tabelle des Typs CTable hinzuzufügen und zu löschen. Diese Version erlaubte das Hinzufügen immer nur am Ende der Tabelle. Diese Nicklichkeit wird jetzt aufgelöst: Wir fügen eine Spalte oder Zeile an dem Platz des angegebenen Index hinzu. 

Beispiel: Hinzufügen einer Spalte am Anfang einer Tabelle, d.h. diese Spalte muss den ersten Index (Index 0) erhalten. Der Array der Spalten muss um eine Spalte erhöht werden, während die Eigenschaften und Werte aller Tabellenzellen um eine Zelle nach rechts verschoben werden müssen, so dass eine leere erste Spalte entsteht. Das selbe Prinzip wird beim Hinzufügen einer neuen Zeile angewendet. 

Das umgekehrte Vorgehen wird für das Entfernen einer Spalte oder Zeile verwendet. Versuchen wir die erste Spalte des vorherigen Beispiels wieder entfernen: zuerst werden alle Daten und Eigenschaften um eine Spalte nach links verschoben und danach wird der Array der Spalten um eins erniedrigt.

Dafür wurde die Hilfsmethode entwickelt, um das Erstellen der Methode zum Hinzufügen oder Löschen von Spalten und Zeilen zu erleichtern. Die Methoden CCanvasTable::ColumnInitialize() und CCanvasTable::CellInitialize() werden zur Initialisierung der Zellen der ergänzten Spalten und Zeilen mit Standardwerten verwendet.

class CCanvasTable : public CElement
  {
private:
   //--- Initialisierung der angegebenen Spalte mit Standardwerten
   void              ColumnInitialize(const uint column_index);
   //--- Initialisierung der angegebenen Zelle mit Standardwerten
   void              CellInitialize(const uint column_index,const uint row_index);
  };
//+------------------------------------------------------------------+
//| Initialisierung der angegebenen Spalte mit Standardwerten        |
//+------------------------------------------------------------------+
void CCanvasTable::ColumnInitialize(const uint column_index)
  {
//--- Initialisierung der Eigenschaften der Spalte mit Standardwerten
   m_columns[column_index].m_x              =0;
   m_columns[column_index].m_x2             =0;
   m_columns[column_index].m_width          =100;
   m_columns[column_index].m_type           =TYPE_STRING;
   m_columns[column_index].m_text_align     =ALIGN_CENTER;
   m_columns[column_index].m_text_x_offset  =m_text_x_offset;
   m_columns[column_index].m_image_x_offset =m_image_x_offset;
   m_columns[column_index].m_image_y_offset =m_image_y_offset;
   m_columns[column_index].m_header_text    ="";
  }
//+------------------------------------------------------------------+
//| Initialisierung der angegebenen Zelle mit Standardwerten         |
//+------------------------------------------------------------------+
void CCanvasTable::CellInitialize(const uint column_index,const uint row_index)
  {
   m_columns[column_index].m_rows[row_index].m_full_text      ="";
   m_columns[column_index].m_rows[row_index].m_short_text     ="";
   m_columns[column_index].m_rows[row_index].m_selected_image =0;
   m_columns[column_index].m_rows[row_index].m_text_color     =m_cell_text_color;
   m_columns[column_index].m_rows[row_index].m_digits         =0;
   m_columns[column_index].m_rows[row_index].m_type           =CELL_SIMPLE;
//--- Standardmäßig enthält die Zelle keine Bilder
   ::ArrayFree(m_columns[column_index].m_rows[row_index].m_images);
  }

Das Kopieren der Eigenschaften einer Spalte erfolgt mit der Methode CCanvasTable::ColumnCopy(). Ihr Code ist unten aufgeführt:

class CCanvasTable : public CElement
  {
private:
   //--- Kopieren der angegebenen Spalte (Quelle) an den neuen Ort (Ziel)
   void              ColumnCopy(const uint destination,const uint source);
  };
//+------------------------------------------------------------------+
//| Kopieren der angegebenen Spalte (Quelle) an den neuen Ort (Ziel) |
//+------------------------------------------------------------------+
void CCanvasTable::ColumnCopy(const uint destination,const uint source)
  {
   m_columns[destination].m_header_text    =m_columns[source].m_header_text;
   m_columns[destination].m_width          =m_columns[source].m_width;
   m_columns[destination].m_type           =m_columns[source].m_type;
   m_columns[destination].m_text_align     =m_columns[source].m_text_align;
   m_columns[destination].m_text_x_offset  =m_columns[source].m_text_x_offset;
   m_columns[destination].m_image_x_offset =m_columns[source].m_image_x_offset;
   m_columns[destination].m_image_y_offset =m_columns[source].m_image_y_offset;
  }

Die Methode CCanvasTable::CellCopy() kopiert die angegebene Zelle in eine andere. Dafür müssen die Indizes der Spalte und der Zeile der Ziel-Zelle und die Indizes der Spalte und Zeile der Quell-Zelle übergeben werden.‌

class CCanvasTable : public CElement
  {
private:
   //--- Kopieren der angegebene Zelle (Quelle) an den neuen Ort (Ziel)
   void              CellCopy(const uint column_dest,const uint row_dest,const uint column_source,const uint row_source);
  };
//+------------------------------------------------------------------+
//| Kopieren der angegebene Zelle (Quelle) an den neuen Ort (Ziel)   |
//+------------------------------------------------------------------+
void CCanvasTable::CellCopy(const uint column_dest,const uint row_dest,const uint column_source,const uint row_source)
  {
   m_columns[column_dest].m_rows[row_dest].m_type           =m_columns[column_source].m_rows[row_source].m_type;
   m_columns[column_dest].m_rows[row_dest].m_digits         =m_columns[column_source].m_rows[row_source].m_digits;
   m_columns[column_dest].m_rows[row_dest].m_full_text      =m_columns[column_source].m_rows[row_source].m_full_text;
   m_columns[column_dest].m_rows[row_dest].m_short_text     =m_columns[column_source].m_rows[row_source].m_short_text;
   m_columns[column_dest].m_rows[row_dest].m_text_color     =m_columns[column_source].m_rows[row_source].m_text_color;
   m_columns[column_dest].m_rows[row_dest].m_selected_image =m_columns[column_source].m_rows[row_source].m_selected_image;
//--- Kopieren der Größe des Arrays von der Quelle zum Ziel
   int images_total=::ArraySize(m_columns[column_source].m_rows[row_source].m_images);
   ::ArrayResize(m_columns[column_dest].m_rows[row_dest].m_images,images_total);
//---
   for(int i=0; i<images_total; i++)
     {
      //--- Kopieren, wenn es Bilder gibt
      if(::ArraySize(m_columns[column_source].m_rows[row_source].m_images[i].m_image_data)<1)
         continue;
      //--- Erstelle eine Kopie des Bildes
      ImageCopy(m_columns[column_dest].m_rows[row_dest].m_images,m_columns[column_source].m_rows[row_source].m_images,i);
     }
  }

Um wiederkehrende Teile des Codes in der Methode zu eliminieren, wurde eine weitere Methode erstellt, die nach den Veränderungen der neu erstellten Tabelle aufgerufen wird. Jedes mal, wenn Spalten oder Zeilen hinzugefügt oder gelöscht wurden, muss die Tabelle neu berechnet und angepasst werden, um sie danach neu zu zeichnen, damit diese Änderungen sichtbar werden. Die Methode CCanvasTable::RecalculateAndResizeTable() dient diesem Zweck. Hier bestimmt der einzige Parameter, ob die Tabelle ganz (true) neuzuzeichnen ist oder (false) nur zu aktualisieren.

class CCanvasTable : public CElement
  {
private:
   //--- Neuberechnen unter Berücksichtigung der letzten Änderungen und Anpassen der Tabelle
   void              RecalculateAndResizeTable(const bool redraw=false);
  };
//+------------------------------------------------------------------+
//| Neuberechnen nach den letzten Änderungen, Anpassen der Tabelle   |
//+------------------------------------------------------------------+
void CCanvasTable::RecalculateAndResizeTable(const bool redraw=false)
  {
//--- Berechnen der Tabellengröße
   CalculateTableSize();
//--- Größenanpassung der Tabelle
   ChangeTableSize();
//--- Aktualisieren der Tabelle
   UpdateTable(redraw);
  }

Die Methoden zum Hinzufügen und Löschen von Spalten und Zeilen sind viel einfacher und leichter zu verstehen, als als die der Tabelle vom Typ CTable. Sie können das leicht selber vergleichen. Hier wird jetzt nur der Code zum Hinzufügen oder Löschen einer Spalte als Beispiel gezeigt. Der Code zum Arbeiten mit Zeilen folgt der gleichen Reihenfolge von Vorgängen wie sie zu Beginn dieses Kapitels beschrieben wurden. Bitte beachten Sie: das Icon einer sortierten Tabelle bewegt sich zusammen mit der sortierten Spalte sowohl beim Hinzufügen wie beim Löschen. Wenn eine sortierte Spalte entfernt wird, wird die Variable des Index der sortierten Spalte auf Null gesetzt.‌

class CCanvasTable : public CElement
  {
public:
   //--- Hinzufügen einer Spalte zur Tabelle am angegebenen Index
   void              AddColumn(const int column_index,const bool redraw=false);
   //--- Löschen der Spalte in der Tabelle mit dem angegebenen Index
   void              DeleteColumn(const int column_index,const bool redraw=false);
  };
//+------------------------------------------------------------------+
//| Hinzufügen einer Spalte zur Tabelle am angegebenen Index         |
//+------------------------------------------------------------------+
void CCanvasTable::AddColumn(const int column_index,const bool redraw=false)
  {
//--- Erhöhen der Arraygröße um eine Spalte
   int array_size=(int)ColumnsTotal();
   m_columns_total=array_size+1;
   ::ArrayResize(m_columns,m_columns_total);
//--- Setzen der Anzahl der Zeilen der Tabelle
   ::ArrayResize(m_columns[array_size].m_rows,m_rows_total);
//--- Anpassen des Index, falls die Größe überschritten wurde
   int checked_column_index=(column_index>=(int)m_columns_total)? (int)m_columns_total-1 : column_index;
//--- Verschieben der anderen Spalten (beginnend vom Ende des Arrays bis zum Index der neuen Spalte)
   for(int c=array_size; c>=checked_column_index; c--)
     {
      //--- Verschieben des Icons der sortierten Tabelle
      if(c==m_is_sorted_column_index && m_is_sorted_column_index!=WRONG_VALUE)
         m_is_sorted_column_index++;
      //--- Index der alten Spalte
      int prev_c=c-1;
      //--- Initialisierung der neuen Spalte mit den Standardwerten
      if(c==checked_column_index)
         ColumnInitialize(c);
      //--- Verschieben der Daten von der alten Spalte in die aktuelle
      else
         ColumnCopy(c,prev_c);
      //---
      for(uint r=0; r<m_rows_total; r++)
        {
         //--- Initialisierung der neuen Spalte mit Standardwerten
         if(c==checked_column_index)
           {
            CellInitialize(c,r);
            continue;
           }
         //--- Verschieben der Daten der alten Spaltenzelle zur aktuellen Spaltenzelle
         CellCopy(c,r,prev_c,r);
        }
     }
//--- Berechnen und Anpassen der Tabelle
   RecalculateAndResizeTable(redraw);
  }
//+------------------------------------------------------------------+
//| Löschen der Spalte einer Tabelle mit angegebenen Index           |
//+------------------------------------------------------------------+
void CCanvasTable::DeleteColumn(const int column_index,const bool redraw=false)
  {
//--- Abruf der Arraygröße der Spalten
   int array_size=(int)ColumnsTotal();
//--- Anpassen des Index, falls die Größe überschritten wurde
   int checked_column_index=(column_index>=array_size)? array_size-1 : column_index;
//--- Verschieben der anderen Spalten (beginnend beim angegebenen Index bis zur letzten Spalte)
   for(int c=checked_column_index; c<array_size-1; c++)
     {
      //--- Verschieben des Icons der sortierten Tabelle
      if(c!=checked_column_index)
        {
         if(c==m_is_sorted_column_index && m_is_sorted_column_index!=WRONG_VALUE)
            m_is_sorted_column_index--;
        }
      //--- Null setzten, wenn die Spalte entfernt wurde
      else
         m_is_sorted_column_index=WRONG_VALUE;
      //--- Index der nächsten Spalte
      int next_c=c+1;
      //--- Verschieben der Daten aus der nächsten Spalte in die aktuelle
      ColumnCopy(c,next_c);
      //--- Verschieben der Daten der nächsten Spaltenzelle in die aktuelle
      for(uint r=0; r<m_rows_total; r++)
         CellCopy(c,r,next_c,r);
     }
//--- Erniedrigen des Spaltenarrays um eine Spalte
   m_columns_total=array_size-1;
   ::ArrayResize(m_columns,m_columns_total);
//--- Berechnen und Anpassen der Tabelle
   RecalculateAndResizeTable(redraw);
  }

Die Methoden, um die Tabelle komplett neuzuerstellen und zu löschen sind auch ganz einfach, anders als die Methoden für die Tabelle des Typs CTable. Jetzt kann die neue Größe einfach mit der Methode CCanvasTable::TableSize() festgelegt werden. Die kleinste Größe wird durch das Löschen der ganzen Tabelle gesetzt, sie beträgt eine Spalte und eine Zeile. Die Variablen mit Werten der gewählten Zeile, Sortierrichtung und dem Index der sortierenden Spalte sind auch auf Null gesetzt, wenn sie gelöscht werden. Die neue Tabellengröße wird berechnet und am Ende beider Methoden gesetzt.

class CCanvasTable : public CElement
  {
public:
   //--- Neuerstellen der Tabelle
   void              Rebuilding(const int columns_total,const int rows_total,const bool redraw=false);   
   //--- Löschen der Tabelle. Es bleiben nur eine Spalte und eine Zeile.
   void              Clear(const bool redraw=false);
  };
//+------------------------------------------------------------------+
//| Neuerstellen der Tabelle                                         |
//+------------------------------------------------------------------+
void CCanvasTable::Rebuilding(const int columns_total,const int rows_total,const bool redraw=false)
  {
//--- Festlegen der neuen Größe
   TableSize(columns_total,rows_total);
//--- Berechnen und Anpassen der Tabelle
   RecalculateAndResizeTable(redraw);
  }
//+------------------------------------------------------------------+
//| Löschen der Tabelle. Es bleiben nur eine Spalte und eine Zeile.  |
//+------------------------------------------------------------------+
void CCanvasTable::Clear(const bool redraw=false)
  {
//--- Setzen der Mindestgröße von 1x1
   TableSize(1,1);
//--- Setzen der Standardwerte
   m_selected_item_text     ="";
   m_selected_item          =WRONG_VALUE;
   m_last_sort_direction    =SORT_ASCEND;
   m_is_sorted_column_index =WRONG_VALUE;
//--- Berechnen und Anpassen der Tabelle
   RecalculateAndResizeTable(redraw);
  }

Und so schaut alles aus:

 Fig. 2. Demonstration der Verwaltung der Tabellengröße.

Fig. 2. Demonstration der Verwaltung der Tabellengröße.


Die Test-Anwendung der obigen Animation kann am Ende des Artikels heruntergeladen werden.


Doppelklick der linken Maustaste

Manchmal kann notwendig sein, ein bestimmtes Ereignis mit einem Doppelklick auszulösen. Implementieren wir diesen Auslöser in der Bibliothek. Dazu fügen wir den Identifikator ON_DOUBLE_CLICK eines Doppelklicks der linken Maustaste der Datei Defines.mqh hinzu:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
...
#define ON_DOUBLE_CLICK             (34) // Doppelklick der linken Maustaste
...

Es wird ein Ereignis des Identifikator ON_DOUBLE_CLICK in der Klasse CMouse erzeugt. In Betriebssystemen wird dieses Ereignis in der Regel durch "Drücken-Loslassen-Drücken" der linken Maustaste erzeugt. Aber Tests mit der Terminalumgebung haben gezeigt, dass es nicht immer gelingt, dies Ereignis gleichzeitig mit dem Ereignis CHARTEVENT_MOUSE_MOVE (Parameter sparam) zu erkennen. Daher wurde dieses Ereignis mit dem Auslöser “Drücken-Loslassen-Drücken-Loslassen” implementiert. Diese Aktionen können korrekt zusammen mit dem Ereignis CHARTEVENT_CLICK erkannt werden. 

Standardmäßig dürfen nicht mehr als 300 Millisekunden zwischen den beiden Klicks liegen. Für das Erkennen des Ereignisses CHARTEVENT_CLICK wird die Methode CMouse::CheckDoubleClick() in der Ereignisbehandlung der Maus verwendet. Zwei 'static' Variablen werden zu Beginn dieser Methode deklariert, um bei jedem Aufruf der Methode die Werte des aktuellen und des vorigen Ticks (die Anzahl der Millisekunden seit Systemstart) zu sichern. Ist die Anzahl der Millisekunden zwischen beiden Werten kleiner als in der Variablen m_pause_between_clicks festgelegt, dann wird ein Ereignis des Doppelklicks der linken Maustaste erzeugt. ‌

//+------------------------------------------------------------------+
//| Klasse zur Abfrage der Mausparameter                             |
//+------------------------------------------------------------------+
class CMouse
  {
private:
   //--- Zeitspanne zwischen zwei Klicks der linken Maustaste (zur Bestimmung des Doppelklicks)
   uint              m_pause_between_clicks;
   //---
private:
   //--- Prüfen auf Doppelklick der linken Maustaste
   bool              CheckDoubleClick(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CMouse::CMouse(void) : m_pause_between_clicks(300)
  {
...
  }
//+------------------------------------------------------------------+
//| Umgang mit dem Ereignis der Maus                                 |
//+------------------------------------------------------------------+
void CMouse::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Umgang mit einem Klick auf dem Chart
   if(id==CHARTEVENT_CLICK)
     {
      //--- Prüfen auf Doppelklick der linken Maustaste
      CheckDoubleClick();
      return;
     }
  }
//+------------------------------------------------------------------+
//| Prüfen auf Doppelklick der linken Maustaste                      |
//+------------------------------------------------------------------+
void CMouse::CheckDoubleClick(void)
  {
   static uint prev_depressed =0;
   static uint curr_depressed =::GetTickCount();
//--- Aktualisieren der Werte
   prev_depressed =curr_depressed;
   curr_depressed =::GetTickCount();
//--- Bestimmen der Zeitspanne zwischen den Klicks
   uint counter=curr_depressed-prev_depressed;
//--- Ist die Zeitspanne kleiner als angegeben, sende eine Nachricht über einen Doppelklick
   if(counter<m_pause_between_clicks)
      ::EventChartCustom(m_chart.ChartId(),ON_DOUBLE_CLICK,counter,0.0,"");
  }

Jetzt vermag die Ereignisbehandlung der Klassen in der Bibliothek auf einen Doppelklick der linken Maustaste irgendwo auf dem Chart zu reagieren, egal ob sich ein Grafikobjekt unter der Maus befindet oder nicht.


Steuerelemente in einer Tabellenzelle

In diesem Artikel beginnen wir mit dem Thema der Steuerelemente in Tabellenzellen. Diese Funktion könnte zum Beispiel beim Erstellen von mehrparametrischen Expert Systeme notwendig werden. Es ist leichter ein solches System über ein grafisches Interface anzusprechen. Beginnen wir damit, solche Steuerelemente den Tabellenzellen hinzuzufügen, mit Kontrollkästchen und Tasten. 

Als ersten benötigen wir die neue Enumeration ENUM_TYPE_CELL der Zelltypen:

//+------------------------------------------------------------------+
//| Enumeration der Typen der Tabellenzellen                         |
//+------------------------------------------------------------------+
enum ENUM_TYPE_CELL
  {
   CELL_SIMPLE   =0,
   CELL_BUTTON   =1,
   CELL_CHECKBOX =2
  };

Standardmäßig werden während der Initialisierung den Tabellenzellen der einfache Typ – CELL_SIMPLE zugewiesen, das heißt “Zelle ohne Steuerelement”. Um den Zelltyp anzufragen oder festzulegen, verwenden wir die Methode CCanvasTable::CellType(), ihr Code ist unten aufgeführt. Wurde einer Zelle der Typ CELL_CHECKBOX (Kontrollkästchen) zugewiesen, verlangt das auch die Bilder, die die Zustände der Kontrollkästchen zeigen. Die kleinst Zahl von Bildern dieses Zelltyps ist zwei. Es ist möglich mehr als zwei Icons zu verwenden, das erlaubt den Einsatz von Kontrollkästchen mit mehreren Parametern.

class CCanvasTable : public CElement
  {
   //--- Bestimmen/Abfragen des Zelltyps
   void              CellType(const uint column_index,const uint row_index,const ENUM_TYPE_CELL type);
   ENUM_TYPE_CELL    CellType(const uint column_index,const uint row_index);
  };
//+------------------------------------------------------------------+
//| Bestimmen des Zelltyps                                           |
//+------------------------------------------------------------------+
void CCanvasTable::CellType(const uint column_index,const uint row_index,const ENUM_TYPE_CELL type)
  {
//--- Prüfen der Arraygrenze
   if(!CheckOutOfRange(column_index,row_index))
      return;
//--- Bestimmen des Zelltyps
   m_columns[column_index].m_rows[row_index].m_type=type;
  }
//+------------------------------------------------------------------+
//| Abfragen des Zelltyps                                            |
//+------------------------------------------------------------------+
ENUM_TYPE_CELL CCanvasTable::CellType(const uint column_index,const uint row_index)
  {
//--- Prüfen der Arraygrenze
   if(!CheckOutOfRange(column_index,row_index))
      return(WRONG_VALUE);
//--- Rückgabe des Typs der angegebenen Spalte
   return(m_columns[column_index].m_rows[row_index].m_type);
  }

Da Bilderreihen für Kontrollkästchen und Tasten in jeder Spalte unterschiedlich groß sein können, müssen wir in der Lage sein die X- und Y-Abstände für jede Spalte einzeln zu bestimmen. Dazu verwenden wir die Methoden CCanvasTable::ImageXOffset() und CCanvasTable::ImageYOffset():

class CCanvasTable : public CElement
  {
public:
   //--- Abstand der Bilder entlang der X- und Y-Achse
   void              ImageXOffset(const int &array[]);
   void              ImageYOffset(const int &array[]);
  };

Eine weitere Ergänzung ist der Modus, der das Deselektieren einer Zeile bei einem erneuten Klick deaktiviert. Dieser Modus funktioniert nur, wenn die Auswahlmodus für Zeilen aktiviert ist.

class CCanvasTable : public CElement
  {
private:
   //--- Kein Deselektieren bei einem erneuten Klick
   bool              m_is_without_deselect;
   //---
public:
   //--- Der "Kein Deselektieren bei einem erneuten Klick" Modus
   void              IsWithoutDeselect(const bool flag)   { m_is_without_deselect=flag;      }
  };

Die separaten Methoden CCanvasTable::PressedRowIndex() und CCanvasTable::PressedCellColumnIndex() werden jetzt zur Bestimmung des Index der geklickten Spalte und Zeile verwendet. Sie wurden bereits beschrieben als Teile der Methode CCanvasTable::OnClickTable(). Ihr ganzer Code mit den Ergänzungen findet sich in den Dateien, die dem Artikel beigefügt sind. Es sollte eigens vermerkt werden, dass die Verwendung der beiden Methoden bei der Bestimmung der durch die linke Maustaste geklickten Zelle. Als Nächstes schauen wir uns an, wo die erhaltenen Indices der Spalte und Zeile übergeben werden.

class CCanvasTable : public CElement
  {
private:
   //--- Rückgabe des Index der geklickten Zeile
   int               PressedRowIndex(void);
   //--- Rückgabe des Spaltenindex der geklickten Zelle
   int               PressedCellColumnIndex(void);
  };

Wurde eine Tabellenzelle geklickt, muss ihr Typ ermittelt werden und, wenn es ein Steuerelement enthält, muss auch ermittelt werden, ob es aktiviert ist. Zu diesem Zweck werden mehrere 'private' Methoden erstellt, die wichtigste ist CCanvasTable::CheckCellElement(). Ihr werden die Indizes der Spalte und der Zeile übergeben, die die Methoden CCanvasTable::PressedRowIndex() und CCanvasTable::PressedCellColumnIndex() zurückgeliefert haben. Die Methode kennt zwei Modi. Je nach dem Ereignis, weswegen die Methode aufgerufen wird, wird der dritte Parameter verwendet, um anzugeben, ob das Ereignis ein Dopplklick ist.

Danach wird der Zelltyp abgefragt und die entsprechende Methode aufgerufen. Enthält die Zelle eine "Taste" , dann wird die Methode CCanvasTable::CheckPressedButton() aufgerufen. Die Methode CCanvasTable::CheckPressedCheckBox() ist für Zellen mit Kontrollkästchen.‌

class CCanvasTable : public CElement
  {
private:
   //--- Prüfen, ob das Steuerelement der Zelle aktiv ist
   bool              CheckCellElement(const int column_index,const int row_index,const bool double_click=false);
  };
//+------------------------------------------------------------------+
//| Prüfen, ob das Steuerelement beim Klick aktiv ist                |
//+------------------------------------------------------------------+
bool CCanvasTable::CheckCellElement(const int column_index,const int row_index,const bool double_click=false)
  {
//--- Verlassen, wenn die Zelle gar kein Steuerelement hat
   if(m_columns[column_index].m_rows[row_index].m_type==CELL_SIMPLE)
      return(false);
//---
   switch(m_columns[column_index].m_rows[row_index].m_type)
     {
      //--- Ist es eine Taste
      case CELL_BUTTON :
        {
         if(!CheckPressedButton(column_index,row_index,double_click))
            return(false);
         //---
         break;
        }
      //--- Ist es ein Kontrollkästchen
      case CELL_CHECKBOX :
        {
         if(!CheckPressedCheckBox(column_index,row_index,double_click))
            return(false);
         //---
         break;
        }
     }
//---
   return(true);
  }

Schauen wir jetzt einmal auf die Struktur der Methoden CCanvasTable::CheckPressedButton() und CCanvasTable::CheckPressedCheckBox(). Die Anzahl der Bilder in der Zelle wird zu Beginn beider Methoden überprüft. Ein Zelle mit einer Taste beinhaltet mindestens ein Icon, Zellen mit einem Kontrollkästchen zwei. Dann muss ermittelt werden, ob das Icon geklickt wurde. Im Falle des Kontrollkästchens, muss eine binäre Umschaltung implementiert werden. Ein einzelner Klick funktioniert nur mit den Icons der Kontrollkästchen. Ein Doppelklick irgendwo in der Zelle kehrt das Kontrollkästchen um. Beide Methoden erzeugen ein Ereignis mit dem entsprechenden Identifikator und garantieren, dass alle Bedingungen erfüllt sind. Der Index des Icons wird als double Parameter übergeben, und der string Parameter bildet einen Zeichenkette aus den Indices von Spalte und Zeile.

class CCanvasTable : public CElement
  {
private:
   //--- Prüfen ob die Taste geklickt wurde
   bool              CheckPressedButton(const int column_index,const int row_index,const bool double_click=false);
   //--- Prüfen, ob das Kontrollkästchen in der Zelle geklickt wurde
   bool              CheckPressedCheckBox(const int column_index,const int row_index,const bool double_click=false);
  };
//+------------------------------------------------------------------+
//| Prüfen ob die Taste geklickt wurde                               |
//+------------------------------------------------------------------+
bool CCanvasTable::CheckPressedButton(const int column_index,const int row_index,const bool double_click=false)
  {
//--- Verlassen, wenn es kein Bild in der Zelle gibt
   if(ImagesTotal(column_index,row_index)<1)
     {
      ::Print(__FUNCTION__," > Assign at least one image to the button cell!");
      return(false);
     }
//--- Ermitteln der relativen Koordinaten unter dem Mauskursor
   int x=m_mouse.RelativeX(m_table);
// --- Ermitteln des rechten Randes des Bildes
   int image_x  =int(m_columns[column_index].m_x+m_columns[column_index].m_image_x_offset);
   int image_x2 =int(image_x+m_columns[column_index].m_rows[row_index].m_images[0].m_image_width);
//--- Verlassen, wenn nicht das Bild geklickt wurde
   if(x>image_x2)
      return(false);
   else
     {
      //--- War es kein Doppelklick, sende eine Nachricht
      if(!double_click)
        {
         int image_index=m_columns[column_index].m_rows[row_index].m_selected_image;
         ::EventChartCustom(m_chart_id,ON_CLICK_BUTTON,CElementBase::Id(),image_index,string(column_index)+"_"+string(row_index));
        }
     }
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Prüfen, ob das Kontrollkästchen in der Zelle geklickt wurde      |
//+------------------------------------------------------------------+
bool CCanvasTable::CheckPressedCheckBox(const int column_index,const int row_index,const bool double_click=false)
  {
//--- Verlassen, wenn es kein Bild in der Zelle gibt
   if(ImagesTotal(column_index,row_index)<2)
     {
      ::Print(__FUNCTION__," > Assign at least two images to the checkbox cell!");
      return(false);
     }
//--- Ermitteln der relativen Koordinaten unter dem Mauskursor
   int x=m_mouse.RelativeX(m_table);
// --- Ermitteln des rechten Randes des Bildes
   int image_x  =int(m_columns[column_index].m_x+m_image_x_offset);
   int image_x2 =int(image_x+m_columns[column_index].m_rows[row_index].m_images[0].m_image_width);
//--- Verlassen, wenn (1) nicht das Bild geklickt wurde und (2) es kein Doppelklick war
   if(x>image_x2 && !double_click)
      return(false);
   else
     {
      //--- Aktueller Index des gewählten Bildes
      int image_i=m_columns[column_index].m_rows[row_index].m_selected_image;
      //--- Bestimmen des nächsten Index des Bildes
      int next_i=(image_i<ImagesTotal(column_index,row_index)-1)? ++image_i : 0;
      //--- Wählen des nächsten Bildes und Aktualisieren der Tabelle
      ChangeImage(column_index,row_index,next_i,true);
      m_table.Update(false);
      //--- Senden einer Nachricht darüber
      ::EventChartCustom(m_chart_id,ON_CLICK_CHECKBOX,CElementBase::Id(),next_i,string(column_index)+"_"+string(row_index));
     }
//---
   return(true);
  }

Im Ergebnis ist der Code der Methoden CCanvasTable::OnClickTable() und CCanvasTable::OnDoubleClickTable() für den Umgang mit Mausklicks in der Tabelle jetzt sehr viel leichter zu verstehen und zu lesen (siehe den Code unten). Es wird ein entsprechendes Ereignis erzeugt in Abhängigkeit des aktivierten Modus und des Zelltyps, der geklickt wurde.

class CCanvasTable : public CElement
  {
private:
   //--- Behandlung eines Tabellenklicks
   bool              OnClickTable(const string clicked_object);
   //--- Behandlung eines Doppelklicks in der Tabelle
   bool              OnDoubleClickTable(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Behandlung eines Klicks in der Tabelle                           |
//+------------------------------------------------------------------+
bool CCanvasTable::OnClickTable(const string clicked_object)
  {
//--- Verlassen, wenn (1) der Modus der Zeilenauswahl deaktiviert ist oder (2) gerade die Spaltenbreite geändert wird
   if(m_column_resize_control!=WRONG_VALUE)
      return(false);
//--- Verlassen, wenn die Bildlaufleiste aktiv ist
   if(m_scrollv.ScrollState() || m_scrollh.ScrollState())
      return(false);
//--- Verlassen, wenn es ein anderer Objektname ist
   if(m_table.Name()!=clicked_object)
      return(false);
//--- Bestimmen der geklickten Zeile
   int r=PressedRowIndex();
//--- Bestimmen der geklickten Zelle
   int c=PressedCellColumnIndex();
//--- Prüfen, wenn das Steuerelement der Zelle aktiv ist
   bool is_cell_element=CheckCellElement(c,r);
//--- Wenn (1) Auswahlmodus für Zeilen aktiv ist und (2) das Steuerelement der Zelle nicht aktiviert ist
   if(m_selectable_row && !is_cell_element)
     {
      //--- Ändern der Farbe
      RedrawRow(true);
      m_table.Update();
      //--- Senden einer Nachricht darüber
      ::EventChartCustom(m_chart_id,ON_CLICK_LIST_ITEM,CElementBase::Id(),m_selected_item,string(c)+"_"+string(r));
     }
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Behandlung eines Doppelklicks in der Tabelle                     |
//+------------------------------------------------------------------+
bool CCanvasTable::OnDoubleClickTable(const string clicked_object)
  {
   if(!m_table.MouseFocus())
      return(false);
//--- Bestimmen der geklickten Zeile
   int r=PressedRowIndex();
//--- Bestimmen der geklickten Zelle
   int c=PressedCellColumnIndex();
//--- Prüfen, ob das Steuerelement in der Zelle aktiviert wurde und die Rückgabe des Ergebnisses
   return(CheckCellElement(c,r,true));
  }

Ereignisse der linken Maustaste in einer Zelle werden von der Ereignisbehandlung des Steuerelementes entsprechend des Ereignisses ON_DOUBLE_CLICK bearbeitet: 

//+------------------------------------------------------------------+
//| Ereignisbehandlung                                               |
//+------------------------------------------------------------------+
void CCanvasTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Handhabung des Doppelklicks der linken Maustaste
   if(id==CHARTEVENT_CUSTOM+ON_DOUBLE_CLICK)
     {
      //--- Klicks in der Tabelle
      if(OnDoubleClickTable(sparam))
         return;
      //---
      return;
     }
  }

Am Ende funktioniert alles wie dargestellt:

 Fig. 3. Demonstration des Einwirkens auf die Steuerelemente von Tabellenzellen.

Fig. 3. Demonstration des Einwirkens auf die Steuerelemente von Tabellenzellen.


Eine Anwendung aus diesem Artikel kann über den Link am Ende heruntergeladen werden.


Schlussfolgerung

Zur Zeit schaut das Schema der Bibliothek zum Erstellen einer grafische Benutzeroberfläche wie folgt aus:

 Fig. 4. Struktur der Bibliothek im augenblicklichen Entwicklungszustand.

Fig. 4. Struktur der Bibliothek im augenblicklichen Entwicklungszustand.


Unten können 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/3104

Beigefügte Dateien |
Das Handeln nach Donchians Kanälen Das Handeln nach Donchians Kanälen
Im Artikel werden einige Strategien aufgrund des Kanals Donchians unter Verwendung verschiedener Indikatorfilter getestet und entwickelt. Es wird die Forschung und die vergleichende Analyse ihrer Arbeit durchgeführt.
Grafische Interfaces X: Updates für die Tabellendarstellung und ein optimierter Code (build 10) Grafische Interfaces X: Updates für die Tabellendarstellung und ein optimierter Code (build 10)
Wir fahren fort neue Elemente der Tabellendarstellung hinzuzufügen (CCanvasTable). Eine Tabelle ist nun in der Lage: die Zeile unter der Maus hervorzuheben; eine Liste von Icons einer Zelle zuzuweisen und eine Methode aus der Liste ein Icon auszuwählen; die Möglichkeit während der Laufzeit Text einer Zelle zu setzen, zu verändern und noch mehr.
Universeller Trend mit grafischem Interface Universeller Trend mit grafischem Interface
In diesem Artikel erstellen wir auf der Basis einer Reihe von Standardindikatoren einen universellen Trend. Ein zusätzliches grafisches Interface erlaubt das Auswählen des Indikatortyps mit seinen Parametern. Der Indikator erscheint in seinem eigene Fenster mit einer Reihe von farbigen Icons.
Fertige Expert Advisors von MQL5 Wizard laufen auf MetaTrader 4 Fertige Expert Advisors von MQL5 Wizard laufen auf MetaTrader 4
Im Artikel wird ein einfacher Emulator der MetaTrader 5 Handelsumgebung für den MetaTrader 4 vorgestellt. Mithilfe des Emulators werden Handelsklassen der Standardbibliothek übertragen und angepasst. Dementsprechend können die Expert Advisors, die MetaTrader 5 Wizard erzeugt, in MetaTrader 4 kompiliert und unverändert gestartet werden.