Grafische Interfaces X: Updates für die Tabellendarstellung und ein optimierter Code (build 10)

Anatoli Kazharski | 2 Mai, 2017


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.

Wir fahren fort die Tabellendarstellung mit weiteren Merkmalen zu vervollständigen (CCanvasTable). Folgende Merkmale werden diesmal ergänzt.

  • Hervorheben der Tabellenzeile unter dem Kursor.
  • Die Möglichkeit jeder Zelle Icons in einem Array zuzuweisen mit einer Methode sie zu wechseln.
  • Die Möglichkeit während der Laufzeit Text einer Zelle zu setzen und verändern. 

Zusätzlich wurde der Code und bestimmte Algorithmen optimiert, um Tabellen schneller neuzuzeichnen. 

 

Relative Koordinaten des Mauskursors vor einem angegebenem Hintergrund

Um doppelten Code für die Berechnung relativen Koordinaten vor einem Hintergrund in vielen Klassen zu eliminieren, wurden die Methoden CMouse::RelativeX() und CMouse::RelativeY() der Klasse CMouse für den Abruf der Koordinaten hinzugefügt. Es muss diesen Methoden eine Referenz auf ein Objekt mit dem Typ CRectCanvas übergeben werden, um, unter Berücksichtigung des aktuellen Abstandes zum sichtbaren Teil des Hintergrundes die relativen Koordinaten zu berechnen.

//+------------------------------------------------------------------+
//| Klasse zur Abfrage der Mausparameter                             |
//+------------------------------------------------------------------+
class CMouse
  {
public:
   //--- Rückgabe der relativen Koordinaten des Mauskursors vor dem übergebenen Hintergrundes
   int               RelativeX(CRectCanvas &object);
   int               RelativeY(CRectCanvas &object);
  };
//+------------------------------------------------------------------+
//| Rückgabe der relativen X-Koordinate des Mauskursor               |
//| des übergebenen Hintergrundobjektes                              |
//+------------------------------------------------------------------+
int CMouse::RelativeX(CRectCanvas &object)
  {
   return(m_x-object.X()+(int)object.GetInteger(OBJPROP_XOFFSET));
  }
//+------------------------------------------------------------------+
//| Rückgabe der relativen Y-Koordinate des Mauskursor               |
//| des übergebenen Hintergrundobjektes                              |
//+------------------------------------------------------------------+
int CMouse::RelativeY(CRectCanvas &object)
  {
   return(m_y-object.Y()+(int)object.GetInteger(OBJPROP_YOFFSET));
      }

In der weiteren Entwicklung der Bibliothek werden diese Methoden dazu verwendet, die relativen Koordinaten aller gezeichnet Kontrollelement abzufragen. 

 

Änderungen in der Struktur der Tabellen

Um die Ausführung des Codes höchst möglichst zu optimieren, war es notwendig den Typ CTOptions der Struktur einer Tabelle etwas zu ändern, zu ergänzen und weitere, neue Strukturen hinzufügen, um so multidimensionale Tabellen zu erstellen. Die Aufgabe hier ist bestimmte Teile der Tabelle auf Basis vorher berechneter Werte neuzuzeichnen. Das könnten zum Beispiel die Koordinaten der Grenzen der Tabellenspalten und -zeilen sein.

So ist zum Beispiel für das Berechnen und Sichern der X-Koordinate der Spaltengrenze nur in der Methode CCanvasTable::DrawGrid() erforderlich, die zum Zeichnen des Gitters der ganzen Tabelle verwendet wird. Und, wenn ein Nutzer eine Tabellenzeile wählt, können die vorgegebenen Werte verwendet werden. Dasselbe gilt für das Hervorheben von Tabellenzeilen, wenn die Maus über einer ist (das wird weiter unten besprochen). 

Wir erstellen eine eigene Struktur (CTRowOptions) und deklarieren einen Array seiner Instanzen, um die Y-Koordinaten der Tabellenzeilen zu sichern und möglicher anderer, zukünftiger Eigenschaften einer Zeile. Die Y-Koordinaten werden in der Methode CCanvasTable::DrawRows() berechnet, die den Hintergrund der Zeilen zeichnet. Das diese Methode vor dem Zeichnen des Gitters aufgerufen wird, verwendet die Methode CCanvasTable::DrawGrid() die vorabberechneten Werte aus der Struktur CTRowOptions

Wir erstellen eigene Struktur des Typs CTCell zum Sichern der Eigenschaften der Zellen. Der Array der Instanzen in der Struktur CTRowOptions wird als dieser Typ deklariert, als ein Array von Tabellenzeilen. Diese Struktur speichert:

  • Array der Icons
  • Array der Icongrößen
  • Index der ausgewählten (angezeigten) Icons einer Zelle
  • Volltext
  • Textteil
  • Schriftfarbe

Da jedes Icon ein Array von Pixeln ist, wird, um sie zu sichern, eine eigene Struktur (CTImage) mit einem dynamischen Array benötigt. Der Code dieser Strukturen findet sich unten:

class CCanvasTable : public CElement
  {
private:
   //--- Array der Pixel der Icons
   struct CTImage { uint m_image_data[]; };
   //--- Eigenschaften der Tabellenzellen
   struct CTCell
     {
      CTImage           m_images[];       // Array der Icons
      uint              m_image_width[];  // Array der Iconbreite
      uint              m_image_height[]; // Array der Iconhöhe
      int               m_selected_image; // Index des gewählten (angezeigten) Icons
      string            m_full_text;      // Volltext
      string            m_short_text;     // Textteil
      color             m_text_color;     // Textfarbe
     };
   //--- Array der Zeilen und der Eigenschaften von Tabellenspalten
   struct CTOptions
     {
      int               m_x;             // X-Koordinate der linken Rand der Spalte
      int               m_x2;            // X-Koordinate der rechten Rand der Spalte
      int               m_width;         // Spaltenbreite
      ENUM_ALIGN_MODE   m_text_align;    // Art der Textausrichtung in der Zelle der Spalte
      int               m_text_x_offset; // Textabstand
      string            m_header_text;   // Text des Spaltenkopfes
      CTCell            m_rows[];        // Array der Tabellenzeilen
     };
   CTOptions         m_columns[];
   //--- Array der Eigenschaften der Tabellenzeilen
   struct CTRowOptions
     {
      int               m_y;  // Y-Koordinate der Oberkante der Zeile
      int               m_y2; // Y-Koordinate der Unterkante der Zeile
     };
   CTRowOptions      m_rows[];
      };

Die entsprechenden Änderungen wurden in allen Methoden vorgenommen, in denen diese Daten verwendet werden. 

 

Bestimmen der Zeilenbreite im sichtbaren Teil

Da eine Tabelle mehrere Zeilen haben kann, würde die Suche nach dem Fokus gefolgt von einem Neuzeichnen der Tabelle die Ausführung signifikant verlangsamen. Dasselbe würde bei der Auswahl von Zeilen und dem Anpassen der Textlängen während einer manuellen Änderung der Spaltenbreite passieren. Um Verzögerungen zu vermeiden, müssen der erste und letzte Index des sichtbaren Teils der Tabelle festgestellt werden, um nur in diesem Bereich mit einer Schleife zu arbeiten. Die Methode CCanvasTable::VisibleTableIndexes() wurde zu diesem Zweck implementiert. Sie bestimmt als erstes die Grenzen des sichtbaren Teils. Die Obergrenze ist der Abstand des sichtbaren Teils entlang der Y-Achse, und die Untergrenze definiert sich aus Obergrenze + Höhe des sichtbaren Teils entlang der Y-Achse.

Jetzt genügt es, die erhaltenen Werte der Grenzen durch Zeilenhöhe aus der Definition der Tabelle zu dividieren, um den Index der obersten und untersten Zeile des sichtbaren Teils zu bestimmen. Falls der Bereich der letzten Zeile überschritten wurde, wird eine Anpassung am Ende der Methode durchgeführt.

class CCanvasTable : public CElement
  {
private:
   //--- Bestimmen der Indizes des sichtbaren Teils der Tabelle
   int               m_visible_table_from_index;
   int               m_visible_table_to_index;
   //---
private:
   //--- Bestimmen der Indizes des sichtbaren Teils der Tabelle
   void              VisibleTableIndexes(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCanvasTable::CCanvasTable(void) : m_visible_table_from_index(WRONG_VALUE),
                                   m_visible_table_to_index(WRONG_VALUE)
  {
...
  }
//+------------------------------------------------------------------+
//| Bestimmen der Indizes des sichtbaren Teils der Tabelle           |
//+------------------------------------------------------------------+
void CCanvasTable::VisibleTableIndexes(void)
  {
//--- Bestimmen der Grenzen unter Berücksichtigung des Abstandes des sichtbaren Teils der Tabelle
   int yoffset1 =(int)m_table.GetInteger(OBJPROP_YOFFSET);
   int yoffset2 =yoffset1+m_table_visible_y_size;
//--- Bestimmen des ersten und letzten Index des sichtbaren Teils der Tabelle
   m_visible_table_from_index =int(double(yoffset1/m_cell_y_size));
   m_visible_table_to_index   =int(double(yoffset2/m_cell_y_size));
//--- Erhöhen des niedrigeren Index um eins, wenn nicht außerhalb
   m_visible_table_to_index=(m_visible_table_to_index+1>m_rows_total)? m_rows_total : m_visible_table_to_index+1;
      }

Der Index wird in der Methode CCanvasTable::DrawTable() bestimmt. Dieser Methode kann ein Argument übergeben werden, so dass nur der sichtbare Teil der Tabelle neuzuzeichnen ist. Standardmäßig ist dieses Argument false, welches ein Neuzeichnen der ganzen Tabelle nachsichzieht. Der Code unten zeigt verkürzt diese Methode.

//+------------------------------------------------------------------+
//| Tabelle zeichnen                                                 |
//+------------------------------------------------------------------+
void CCanvasTable::DrawTable(const bool only_visible=false)
  {
//--- Falls nicht nur der sichtbare Teil der Tabelle neu gezeichnet werden soll
   if(!only_visible)
     {
      //--- Setzen der Zeilenindizes der ganzen Tabelle von Anfang bis Ende
      m_visible_table_from_index =0;
      m_visible_table_to_index   =m_rows_total;
     }
//--- Abfragen des Zeilenindex des sichtbaren Teils der Tabelle
   else
      VisibleTableIndexes();
//--- Zeichnen des Hintergrundes der Tabellenzeile
//--- Zeichnen der gewählten Zeile
//--- Zeichnen des Gitters
//--- Zeichnen des Icons
//--- Textausgabe
//--- Anzeigen der letzten gezeichneten Änderungen
//--- Aktualisieren der Spaltenköpfe, wenn sie aktiv sind
//--- Anpassen der Tabelle relativ zu den Bildlaufleisten
      }

Es muss in dieser Methode auch CCanvasTable::VisibleTableIndexes() aufgerufen werden, um den Fokus der Tabellenzeilen zu bestimmen: 

//+------------------------------------------------------------------+
//| Prüfen des Fokus auf die Tabellenzeilen                          |
//+------------------------------------------------------------------+
int CCanvasTable::CheckRowFocus(void)
  {
   int item_index_focus=WRONG_VALUE;
//--- Abfrage der relativen Y-Koordinate unter dem Mauskursor
   int y=m_mouse.RelativeY(m_table);
///--- Abfrage des Index des lokalen Bereiches der Tabelle
   VisibleTableIndexes();
//--- Suchen des Fokus
   for(int i=m_visible_table_from_index; i<m_visible_table_to_index; i++)
     {
      //--- Wenn sich der Fokus auf Zeile ändert
      if(y>m_rows[i].m_y && y<=m_rows[i].m_y2)
        {
         item_index_focus=i;
         break;
        }
     }
//--- Rückgabe des Index der Zeile im Fokus
   return(item_index_focus);
      }

 

 

Icons in Tabellenzellen

Es können jeder Zelle mehrere Icons zugewiesen werden, die während der Laufzeit gewechselt werden können. Die neuen Variablen und Methoden für den Abstand der Icons vom oberen und linken Rand der Zelle:

class CCanvasTable : public CElement
  {
private:
   //--- Iconabstand vom Zellenrand
   int               m_image_x_offset;
   int               m_image_y_offset;
   //---
public:
   //--- Iconabstand vom Zellenrand
   void              ImageXOffset(const int x_offset)     { m_image_x_offset=x_offset;       }
   void              ImageYOffset(const int y_offset)     { m_image_y_offset=y_offset;       }
      };

Um ein Icon einer angegebenen Zelle zuzuweisen, muss ein Array mit seinem Pfad des lokalen Verzeichnisses des Terminals übergeben werden. Davor müssen sie von der MQL-Anwendung als Ressource (#resource) geladen werden. Die Methode CCanvasTable::SetImages() erfüllt diesen Zweck. Wird eine leerer Array übergeben oder ein Array-Überlauf registriert, wird die Methode verlassen.

Nach bestandenen Prüfungen werden die Arrays der Zellen neu dimensioniert. Danach werden in einer Schleife mit der Methode ::ResourceReadImage() die Icons in einen eindimensionalen Array eingelesen mit der Farbe für jeden Pixel. Die Icongrößen werden im entsprechenden Array gesichert. Die werden für die Dimension der Schleife zum Zeichnen der Icons auf dem Hintergrund benötigt. Standardmäßig ist immer das erste Icon einer Zelle ausgewählt.

class CCanvasTable : public CElement
  {
public:
   //--- Bestimmen der Icons für die angegebenen Zelle
   void              SetImages(const uint column_index,const uint row_index,const string &bmp_file_path[]);
  };
//+------------------------------------------------------------------+
//| Bestimmen der Icons für die angegebenen Zelle                    |
//+------------------------------------------------------------------+
void CCanvasTable::SetImages(const uint column_index,const uint row_index,const string &bmp_file_path[])
  {
   int total=0;
//--- Verlassen, wenn der Array leer ist
   if((total=CheckArraySize(bmp_file_path))==WRONG_VALUE)
      return;
//--- Prüfen der Arraygrenze
   if(!CheckOutOfRange(column_index,row_index))
      return;
//--- Größenänderung des Arrays
   ::ArrayResize(m_columns[column_index].m_rows[row_index].m_images,total);
   ::ArrayResize(m_columns[column_index].m_rows[row_index].m_image_width,total);
   ::ArrayResize(m_columns[column_index].m_rows[row_index].m_image_height,total);
//---
   for(int i=0; i<total; i++)
     {
      //--- Standardauswahl des ersten Icons
      m_columns[column_index].m_rows[row_index].m_selected_image=0;
      //--- Schreiben des übergebenen Icons in den Array und sichern dessen Größe
      if(!ResourceReadImage(bmp_file_path[i],m_columns[column_index].m_rows[row_index].m_images[i].m_image_data,
         m_columns[column_index].m_rows[row_index].m_image_width[i],
         m_columns[column_index].m_rows[row_index].m_image_height[i]))
        {
         Print(__FUNCTION__," > error: ",GetLastError());
         return;
        }
     }
      }

Um herauszufinden, wie viele Icons eine bestimmte Zelle hat, verwenden wir die Methode CCanvasTable::ImagesTotal():

class CCanvasTable : public CElement
  {
public:
   //--- Rückgabe der Gesamtzahl der Icons der angegebenen Zelle
   int               ImagesTotal(const uint column_index,const uint row_index);
  };
//+------------------------------------------------------------------+
//| Rückgabe der Gesamtzahl der Icons der angegebenen Zelle          |
//+------------------------------------------------------------------+
int CCanvasTable::ImagesTotal(const uint column_index,const uint row_index)
  {
//--- Prüfen der Arraygrenze
   if(!CheckOutOfRange(column_index,row_index))
      return(WRONG_VALUE);
//--- Rückgabe der Arraygröße der Icons
   return(::ArraySize(m_columns[column_index].m_rows[row_index].m_images));
      }

Betrachten wir jetzt die Methoden zum Zeichnen der Icons. Zuerst wurde eine neue Methode CColors::BlendColors() der Klasse CColors ergänzt, die die obere und untere Farbe unter Berücksichtigung der Transparenz des darüber gelegten Icons mischt. So wie die Hilfsmethode CColors::GetA(), die den Transparenzwert der übergebenen Farbe ermittelt.

In der Methode CColors::BlendColors() wird die übergebene Farbe in ihre RGB-Komponenten aufgeteilt, und der Alphakanal wird aus der obersten Farbe extrahiert. Der Alphakanal wird in einen Wert zwischen Null und Eins konvertiert. Ist die übergebene Farbe intransparent, wird die Farbvermischung nicht durchgeführt. Im Falle von Transparenz wird jede Komponente der beiden Farben unter Berücksichtigung der oberen Farbe gemischt. Danach werden die Werte der erhaltenen Komponenten angepasst, sollten sie den Wert von 255 übersteigen. 

//+------------------------------------------------------------------+
//| Klasse für das Arbeiten mit Farben                               |
//+------------------------------------------------------------------+
class CColors
  {
public:
   double            GetA(const color aColor);
   color             BlendColors(const uint lower_color,const uint upper_color);
  };
//+------------------------------------------------------------------+
//| Ermitteln der A-Komponente des Wertes                            |
//+------------------------------------------------------------------+
double CColors::GetA(const color aColor)
  {
   return(double(uchar((aColor)>>24)));
  }
//+------------------------------------------------------------------+
//| Mischen zweier Farben gemäß der Transparenz der oberen Farbe     |
//+------------------------------------------------------------------+
color CColors::BlendColors(const uint lower_color,const uint upper_color)
  {
   double r1=0,g1=0,b1=0;
   double r2=0,g2=0,b2=0,alpha=0;
   double r3=0,g3=0,b3=0;
//--- Konvertieren der Farbe in das Format ARGB
   uint pixel_color=::ColorToARGB(upper_color);
//--- Ermitteln der Komponenten der unteren und oberen Farbe
   ColorToRGB(lower_color,r1,g1,b1);
   ColorToRGB(pixel_color,r2,g2,b2);
//--- Ermitteln der Prozentsatzes der Transparenz zw. 0.00 und 1.00
   alpha=GetA(upper_color)/255.0;
//--- Gibt es Transparenz
   if(alpha<1.0)
     {
      //--- Mischen beider Farben gemäß des Alphakanals
      r3=(r1*(1-alpha))+(r2*alpha);
      g3=(g1*(1-alpha))+(g2*alpha);
      b3=(b1*(1-alpha))+(b2*alpha);
      //--- Anpassen der erhaltenen Werte
      r3=(r3>255)? 255 : r3;
      g3=(g3>255)? 255 : g3;
      b3=(b3>255)? 255 : b3;
     }
   else
     {
      r3=r2;
      g3=g2;
      b3=b2;
     }
//--- Kombinieren der Komponenten und Rückgabe der Farbe
   return(RGBToColor(r3,g3,b3));
      }

Damit wird es leicht eine Methode zu schreibe, die die Icons zeichnet. Der Code der Methode CCanvasTable::DrawImage() ist unten angeführt. Ihr muss der Index der Zelle übergeben werden, auf der das Icon gezeichnet werden soll. Zu Beginn der Methode werden die Koordinaten des Icons unter Berücksichtigung der Abstände ermittelt, so wie der Index der gewählten Zelle und ihre Größe. Dann liefert eine Doppelschleife das Icon, Pixel für Pixel. Ist ein Pixel leer, d.h. ohne angegebene Farbe, geht die Schleife zum nächsten Pixel. Gibt es eine Farbe, werden die Farbe des Zellhintergrunds und die aktuelle Pixelfarbe bestimmt und beide Farben werden unter Berücksichtigung der Transparenz der oberen Farbe vermischt und die sich ergebende Farbe wird auf dem Hintergrund gezeichnet.

class CCanvasTable : public CElement
  {
private:
   //--- Zeichnen eines Icons in der angegebenen Zelle
   void              DrawImage(const int column_index,const int row_index);
  };
//+------------------------------------------------------------------+
//| Zeichnen eines Icons in der angegebenen Zelle                    |
//+------------------------------------------------------------------+
void CCanvasTable::DrawImage(const int column_index,const int row_index)
  {
//--- Berechnen der Koordinaten
   int x =m_columns[column_index].m_x+m_image_x_offset;
   int y =m_rows[row_index].m_y+m_image_y_offset;
//--- Ausgewähltes Icon der Zelle mit seiner Größe
   int  selected_image =m_columns[column_index].m_rows[row_index].m_selected_image;
   uint image_height   =m_columns[column_index].m_rows[row_index].m_image_height[selected_image];
   uint image_width    =m_columns[column_index].m_rows[row_index].m_image_width[selected_image];
//--- Zeichnen
   for(uint ly=0,i=0; ly<image_height; ly++)
     {
      for(uint lx=0; lx<image_width; lx++,i++)
        {
         //--- Gibt es keine Farbe, gehe zum nächsten Pixel
         if(m_columns[column_index].m_rows[row_index].m_images[selected_image].m_image_data[i]<1)
            continue;
         //--- Ermitteln der Farbe der unteren Schicht (Zellhintergrund) und der Farbe des jew. Pixels des Icons
         uint background  =(row_index==m_selected_item)? m_selected_row_color : m_table.PixelGet(x+lx,y+ly);
         uint pixel_color =m_columns[column_index].m_rows[row_index].m_images[selected_image].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_table.PixelSet(x+lx,y+ly,foreground);
        }
     }
      }

Die Methode CCanvasTable::DrawImages() zeichnet alle Icons auf einmal in die Tabelle, abhängig davon, ob nur der sichtbare Teil der Tabelle gezeichnet werden muss. In der aktuellen Version der Tabelle können die Icons nur dann gezeichnet werden, wenn die Textausrichtung linksbündig ist. Zusätzlich wird in jeder Iteration geprüft, ob ein Icon der Zelle zugewiesen ist und ob dessen Array der Pixel leer ist. Nach allen Prüfungen wird die Methode CCanvasTable::DrawImage() zum Zeichnen der Icons aufgerufen

class CCanvasTable : public CElement
  {
private:
   //--- Zeichnen aller Icons der Tabelle
   void              DrawImages(void);
  };
//+------------------------------------------------------------------+
//| Zeichnen aller Icons der Tabelle                                 |
//+------------------------------------------------------------------+
void CCanvasTable::DrawImages(void)
  {
//--- Berechnen der Koordinaten
   int x=0,y=0;
//--- Spalten
   for(int c=0; c<m_columns_total; c++)
     {
      //--- Wenn die Textausrichtung nicht linksbündig ist, gehe zur nächsten Spalte
      if(m_columns[c].m_text_align!=ALIGN_LEFT)
         continue;
      //--- Zeilen
      for(int r=m_visible_table_from_index; r<m_visible_table_to_index; r++)
        {
         //--- Nächste Zelle, wenn dieser kein Icon zugewiesen wurde
         if(ImagesTotal(c,r)<1)
            continue;
         //--- Das gewählte Icon der Zelle (standardmäßig das erste [0])
         int selected_image=m_columns[c].m_rows[r].m_selected_image;
         //--- Nächste Zelle, wenn das Array der Pixel leer ist
         if(::ArraySize(m_columns[c].m_rows[r].m_images[selected_image].m_image_data)<1)
            continue;
         //--- Zeichnen des Icons
         DrawImage(c,r);
        }
     }
      }

Der Screenshot unten zeigt das Beispiel einer Tabelle mit Icons in den Zellen:

 Fig. 1. Tabelle mit Icons in den Zellen.

Fig. 1. Tabelle mit Icons in den Zellen. 


 

Hervorheben der Tabellenzeile unter dem Kursor

Um eine Zeile der gezeigten Tabelle hervorzuheben, wenn sich die Maus über ihr befindet, benötigen wir weitere Variablen und Methoden. Um das Hervorheben zu aktivieren, verwenden wir die Methode CCanvasTable::LightsHover(). Die Zeilenfarbe wird mit Hilfe der Methode CCanvasTable::CellColorHover() festgelegt.

class CCanvasTable : public CElement
  {
private:
   //--- Zellfarben in verschiedenen Modi
   color             m_cell_color;
   color             m_cell_color_hover;
   //--- Modus des Hervorhebens der Zeile unter der Maus
   bool              m_lights_hover;
   //---
public:
   //--- Zellfarben in verschiedenen Modi
   void              CellColor(const color clr)           { m_cell_color=clr;                }
   void              CellColorHover(const color clr)      { m_cell_color_hover=clr;          }
   //--- Modus des Hervorhebens der Zeile unter der Maus
   void              LightsHover(const bool flag)         { m_lights_hover=flag;             }
      };

Das Hervorheben einer Zeile muss nicht ein wiederholtes Neuzeichnen der ganzen Tabelle zur Folge haben, sobald sich die Maus etwas bewegt. Im Gegenteil, das würde massiv die CPU belasten, die Ausführung der Anwendung stark verzögern und ist daher nicht zu empfehlen. Erreicht die Maus das erste Mal oder erneut einen Bereich der Tabelle, genügt es, sich einmal auf den Fokus zu konzentrieren (Iteration über den ganzen Array der Zeilen). Dafür gibt es die Methode CCanvasTable::CheckRowFocus(). Ist der Fokus und der Index dieser Zeile erst gefunden, muss nur noch geprüft werden, ob bei einer Mausbewegung sich die Zeile im Fokus geändert hat. Der beschrieben Algorithmus wurde in der Methode CCanvasTable::ChangeRowsColor() umgesetzt, wie man unten sieht. Die Methode CCanvasTable::RedrawRow() verändert die Zeilenfarbe, ihr Code wird später aufgeführt. Die Methode CCanvasTable::ChangeRowsColor() wird in der Methode CCanvasTable::ChangeObjectsColor() aufgerufen, um die Farbe des Tabellenobjekts zu ändern. 

class CCanvasTable : public CElement
  {
private:
   //--- Bestimmen des Zeilenfokus
   int               m_item_index_focus;
   //--- Bestimmen des Momentes, da der Mauskursor von einer Zeile zur nächsten wechselt
   int               m_prev_item_index_focus;
   //---
private:
   //--- Wechseln der Farbe des Hervorhebens
   void              ChangeRowsColor(void);
  };
//+------------------------------------------------------------------+
//| Wechseln der Farbe des Hervorhebens                              |
//+------------------------------------------------------------------+
void CCanvasTable::ChangeRowsColor(void)
  {
//--- Verlassen, wenn das Hervorheben deaktiviert ist
   if(!m_lights_hover)
      return;
//--- Falls nicht im Fokus
   if(!m_table.MouseFocus())
     {
      //--- Falls noch nicht bekannt, dass es noch nicht im Fokus ist
      if(m_prev_item_index_focus!=WRONG_VALUE)
        {
         m_item_index_focus=WRONG_VALUE;
         //--- Ändern der Farbe
         RedrawRow();
         m_table.Update();
         //--- Rücksetzen des Fokus
         m_prev_item_index_focus=WRONG_VALUE;
        }
     }
//--- Wenn im Fokus
   else
     {
      //--- Prüfen des Fokus auf die Tabellenzeilen
      if(m_item_index_focus==WRONG_VALUE)
        {
         //--- Ermitteln des Index der Zeile im Fokus
         m_item_index_focus=CheckRowFocus();
         //--- Ändern der Zeilenfarbe
         RedrawRow();
         m_table.Update();
         //--- Sichern als vorheriger Index des Fokus
         m_prev_item_index_focus=m_item_index_focus;
         return;
        }
      //--- Abfrage der relativen Y-Koordinate unter dem Mauskursor
      int y=m_mouse.RelativeY(m_table);
      //--- Bestätigen des Fokus
      bool condition=(y>m_rows[m_item_index_focus].m_y && y<=m_rows[m_item_index_focus].m_y2);
      //--- Bei geändertem Fokus
      if(!condition)
        {
         //--- Ermitteln des Index der Zeile im Fokus
         m_item_index_focus=CheckRowFocus();
         //--- Ändern der Zeilenfarbe
         RedrawRow();
         m_table.Update();
         //--- Sichern als vorheriger Index des Fokus
         m_prev_item_index_focus=m_item_index_focus;
        }
     }
      }

Die Methode CCanvasTable::RedrawRow() gewährleistet ein schnelles Neuzeichnen in zwei Modi:

  •  bei der Auswahl einer Zeile
  •  im Modus Hervorheben der Zeile unter der Maus.

Die Methode verlangt das entsprechende Argument, um den gewünschten Modus zu bestimmen. Standardmäßig steht dieses Argument auf false, und das steht für den Modus für das Hervorheben von Zeilen unter der Maus. Die Klasse beinhaltet spezielle Variablen für beide Modi, um die aktuellen und die vorher ausgewählte/hervorgehobene Tabellenzeile zu bestimmen. Daher verlangt das Markieren einer neuen Zeile nur das Neuzeichnen der vorherigen und der aktuellen Zeile und nicht ganzen Tabelle.

Die Methode wird verlassen, wenn die Indices nicht definiert sind (WRONG_VALUE). Danach muss ermittelt werden, wie viel Indices definiert wurden. Bewegt sich die Maus das erste Mal in die Tabelle und gibt es nur einen Index (den aktuellen), wird nur die Farbe der aktuellen Zeile geändert. Wird die Maus erneut in die Tabelle bewegt, werden zwei Zeilen geändert (die neue und die letzte). 

Jetzt muss die Reihenfolge der Farbänderungen bestimmt werden. Ist der Index der aktuellen Zeile größer als der der vorherigen, hat sich der Kursor nach unten bewegt. Dann wird zuerst die Farbe der vorherigen Zeile geändert und dann die aktuelle. Im umgekehrten Fall, kehren wir das Vorgehen um. Die Methode berücksichtigt auch den Moment des Verlassens der Tabelle, wenn keine aktuelle Zeile bestimmt wurde, es aber noch einen Index für eine vorherige Zeile gibt.

Sind erst einmal alle operativen Variablen initiiert, dann werden der Zeilenhintergrund, das Gitter und die Texte in strenger Reihenfolge gezeichnet.

class CCanvasTable : public CElement
  {
private:
   //--- Neuzeichnen der angegebenen Tabellenzeile gemäß des angegebenen Modus
   void              RedrawRow(const bool is_selected_row=false);
  };
//+------------------------------------------------------------------+
//| Neuzeichnen der best. Tabellenzeile gemäß des angegebenen Modus  |
//+------------------------------------------------------------------+
void CCanvasTable::RedrawRow(const bool is_selected_row=false)
  {
//--- Der aktuelle und der vorherige Zeilenindex
   int item_index      =WRONG_VALUE;
   int prev_item_index =WRONG_VALUE;
//--- Initialisierung der Zeilenindizes gemäß des angegebenen Modus
   if(is_selected_row)
     {
      item_index      =m_selected_item;
      prev_item_index =m_prev_selected_item;
     }
   else
     {
      item_index      =m_item_index_focus;
      prev_item_index =m_prev_item_index_focus;
     }
//--- Verlassen, wenn die Indizes nicht definiert sind
   if(prev_item_index==WRONG_VALUE && item_index==WRONG_VALUE)
      return;
//--- Anzahl der zu zeichnenden Zeilen und Spalten
   int rows_total    =(item_index!=WRONG_VALUE && prev_item_index!=WRONG_VALUE)? 2 : 1;
   int columns_total =m_columns_total-1;
//--- Koordinaten
   int x1=1,x2=m_table_x_size;
   int y1[2]={0},y2[2]={0};
//--- Array der Werte in einer besonderen Reihenfolge
   int indexes[2];
//--- Wenn (1) der Mauskursor sich abwärts oder (2) sich das erste Mal in die Tabelle bewegte
   if(item_index>m_prev_item_index_focus || item_index==WRONG_VALUE)
     {
      indexes[0]=(item_index==WRONG_VALUE || prev_item_index!=WRONG_VALUE)? prev_item_index : item_index;
      indexes[1]=item_index;
     }
//--- Wenn der Kursor sich aufwärts bewegte
   else
     {
      indexes[0]=item_index;
      indexes[1]=prev_item_index;
     }
//--- Zeichnen des Zeilenhintergrundes
   for(int r=0; r<rows_total; r++)
     {
      //--- Berechnen der Koordinaten der Ober- und Untergrenzen der Zeile
      y1[r]=m_rows[indexes[r]].m_y+1;
      y2[r]=m_rows[indexes[r]].m_y2-1;
      //--- Bestimmen der Zeile im Fokus unter Berücksichtigung des Modus des Hervorhebens
      bool is_item_focus=false;
      if(!m_lights_hover)
         is_item_focus=(indexes[r]==item_index && item_index!=WRONG_VALUE);
      else
         is_item_focus=(item_index==WRONG_VALUE)?(indexes[r]==prev_item_index) :(indexes[r]==item_index);
      //--- Zeichnen des Zeilenhintergrundes
      m_table.FillRectangle(x1,y1[r],x2,y2[r],RowColorCurrent(indexes[r],is_item_focus));
     }
//--- Gitterfarbe
   uint clr=::ColorToARGB(m_grid_color);
//--- Zeichnen der Grenzen
   for(int r=0; r<rows_total; r++)
     {
      for(int c=0; c<columns_total; c++)
         m_table.Line(m_columns[c].m_x2,y1[r],m_columns[c].m_x2,y2[r],clr);
     }
//--- Zeichnen der Icons
   for(int r=0; r<rows_total; r++)
     {
      for(int c=0; c<m_columns_total; c++)
        {
         //--- Zeichnen des Icons, wenn (1) es in dieser Zelle existiert und (2) die Textausrichtung dieser Spalte linksbündig ist
         if(ImagesTotal(c,r)>0 && m_columns[c].m_text_align==ALIGN_LEFT)
            DrawImage(c,indexes[r]);
        }
     }
//--- Berechnen der Koordinaten
   int x=0,y=0;
//--- Modus der Textausrichtung
   uint text_align=0;
//--- Zeichnen des Textes
   for(int c=0; c<m_columns_total; c++)
     {
      //--- Ermitteln (1) der X-Koordinate des Textes und (2) die Textausrichtung
      x          =TextX(c);
      text_align =TextAlign(c,TA_TOP);
      //---
      for(int r=0; r<rows_total; r++)
        {
         //--- (1) Berechnen der Koordinaten und (2) Zeichnen des Textes
         y=m_rows[indexes[r]].m_y+m_text_y_offset;
         m_table.TextOut(x,y,m_columns[c].m_rows[indexes[r]].m_short_text,TextColor(c,indexes[r]),text_align);
        }
     }
      }

Und das ist das Ergebnis:

 Fig. 2. Demonstration des Hervorhebens der Tabellenzeile unter der Maus.

Fig. 2. Demonstration des Hervorhebens der Tabellenzeile unter der Maus. 

 

 

Methoden die Tabellenzellen neu zu zeichnen

Die Methode für das schnelle Neuzeichnen der Tabellenzeilen wurde bereits angesprochen. Die Methode für das schnelle Neuzeichnen einer Zelle wird jetzt gezeigt. Wenn es zum Beispiel notwendig ist, den Text, seine Farbe oder das Icon einer Zelle der Tabelle zu ändern, reicht es nur diese Zelle neu zu zeichnen statt der ganzen Tabelle. Für diesen Zweck verwenden wir die 'private' Methode CCanvasTable::RedrawCell(). Es wird nur der Zellinhalt neu gezeichnet, alles andere nicht berührt. Die Hintergrundfarbe wird unter Berücksichtigung des Modus des Hervorhebens bestimmt, wenn aktiviert. Nach dem Bestimmen der Werte und der Initialisierung der lokalen Variablen, werden der Hintergrund, das Icon (falls zugewiesen und linksbündigem Text) und der Text in der Zelle neu gezeichnet.

class CCanvasTable : public CElement
  {
private:
   //--- Neuzeichnen der angegebenen Zelle der Tabelle
   void              RedrawCell(const int column_index,const int row_index);
  };
//+------------------------------------------------------------------+
//| Neuzeichnen der angegebenen Zelle der Tabelle                    |
//+------------------------------------------------------------------+
void CCanvasTable::RedrawCell(const int column_index,const int row_index)
  {
//--- Koordinaten
   int x1=m_columns[column_index].m_x+1;
   int x2=m_columns[column_index].m_x2-1;
   int y1=m_rows[row_index].m_y+1;
   int y2=m_rows[row_index].m_y2-1;
//--- Berechnen der Koordinaten
   int  x=0,y=0;
//--- Prüfen des Fokus
   bool is_row_focus=false;
//--- Hervorheben-Modus ist aktiviert
   if(m_lights_hover)
     {
      //--- (1) Ermitteln der relativen Y-Koordinate des Mauskursors und (2) des Fokus der angegebenen Tabellenzeile
      y=m_mouse.RelativeY(m_table);
      is_row_focus=(y>m_rows[row_index].m_y && y<=m_rows[row_index].m_y2);
     }
//--- Zeichnen des Zellhintergrunds
   m_table.FillRectangle(x1,y1,x2,y2,RowColorCurrent(row_index,is_row_focus));
//--- Zeichnen des Icons, wenn (1) es in dieser Zelle existiert und (2) die Textausrichtung dieser Spalte linksbündig ist
   if(ImagesTotal(column_index,row_index)>0 && m_columns[column_index].m_text_align==ALIGN_LEFT)
      DrawImage(column_index,row_index);
//--- Abfrage der Textausrichtung
   uint text_align=TextAlign(column_index,TA_TOP);
//--- Zeichnen des Textes
   for(int c=0; c<m_columns_total; c++)
     {
      //--- Abfragen der X-Koordinate des Textes
      x=TextX(c);
      //--- Schleifenende
      if(c==column_index)
         break;
     }
//--- (1) Berechnen der Y-Koordinate, und (2) Zeichnen des Texte
   y=y1+m_text_y_offset-1;
   m_table.TextOut(x,y,m_columns[column_index].m_rows[row_index].m_short_text,TextColor(column_index,row_index),text_align);
      }

Kommen wir nun zu den Methoden, um in der Zelle den Text, die Textfarbe und das Icon (ausgewählt aus den Zugewiesenen) zu ändern. Die 'public' Methoden CCanvasTable::SetValue() und CCanvasTable::TextColor() müssen für den Text und seine Farbe verwendet werden. Diesen Methoden werden die Indizes der Zelle (Spalte und Zeile) und der einzutragene Wert übergeben. Der Methode CCanvasTable::SetValue() muss eine Zeichenkette übergeben werden, der von der Zelle angezeigt wird. Sie sichert sowohl den ganzen Text wie auch die verkürzte Version (wenn der ganze Text zu lang ist für die Zellbreite) in den entsprechenden Variablen der Struktur der Tabelle (CTCell). Die Textfarbe muss der Methode CCanvasTable::TextColor() übergeben werden. Als vierten Parameter kann für beide Methoden angegeben werden, ob es notwendig ist, die Zelle sofort oder erst später neuzuzeichnen ist, und zwar durch den Aufruf der Methode CCanvasTable::UpdateTable().

class CCanvasTable : public CElement
  {
private:
   //--- Eintragen des Wertes in die angegebene Zelle
   void              SetValue(const uint column_index,const uint row_index,const string value,const bool redraw=false);
   //--- Bestimmen der Farbe der angegebenen Zelle
   void              TextColor(const uint column_index,const uint row_index,const color clr,const bool redraw=false);
  };
//+------------------------------------------------------------------+
//| Ausfüllen des Arrays am den angegebenen Indizes                  |
//+------------------------------------------------------------------+
void CCanvasTable::SetValue(const uint column_index,const uint row_index,const string value,const bool redraw=false)
  {
//--- Prüfen der Arraygrenze
   if(!CheckOutOfRange(column_index,row_index))
      return;
//--- Sichern der Werte im Array
   m_columns[column_index].m_rows[row_index].m_full_text=value;
//--- Anpassen und Sichern des Textes, wenn er nicht in die Zelle passt
   m_columns[column_index].m_rows[row_index].m_short_text=CorrectingText(column_index,row_index);
//--- Neuzeichnen der Zelle, wenn angegeben
   if(redraw)
      RedrawCell(column_index,row_index);
  }
//+------------------------------------------------------------------+
//| Ausfüllen des Farbarrays des Textes                              |
//+------------------------------------------------------------------+
void CCanvasTable::TextColor(const uint column_index,const uint row_index,const color clr,const bool redraw=false)
  {
//--- Prüfen der Arraygrenze
   if(!CheckOutOfRange(column_index,row_index))
      return;
//--- Sichern der Textfarbe im allgemeinen Array
   m_columns[column_index].m_rows[row_index].m_text_color=clr;
//--- Neuzeichnen der Zelle, wenn angegeben
   if(redraw)
      RedrawCell(column_index,row_index);
      }

Das Icon der Zelle wird mit der Methode CCanvasTable::ChangeImage() geändert. Der Index der zu ändernden Icons muss als dritter Parameter hier übergeben werden. So wie in den oben beschriebenen Methoden zum Ändern der Zelleigenschaften ist es möglich anzugeben, ob die Zelle sofort oder später neuzuzeichnen sind

class CCanvasTable : public CElement
  {
private:
   //--- Ändern des Icons der angegebenen Zelle
   void              ChangeImage(const uint column_index,const uint row_index,const uint image_index,const bool redraw=false);
  };
//+------------------------------------------------------------------+
//| Ändern des Icons der angegebenen Zelle                           |
//+------------------------------------------------------------------+
void CCanvasTable::ChangeImage(const uint column_index,const uint row_index,const uint image_index,const bool redraw=false)
  {
//--- Prüfen der Arraygrenze
   if(!CheckOutOfRange(column_index,row_index))
      return;
//--- Ermitteln der Anzahl der Icons der Zelle
   int images_total=ImagesTotal(column_index,row_index);
//--- Verlassen, wenn (1) es keine Icons gibt oder (2) außerhalb des erlaubten Bereiches
   if(images_total==WRONG_VALUE || image_index>=(uint)images_total)
      return;
//--- Verlassen, wenn das angegebene Icon dem gewählten entspricht
   if(image_index==m_columns[column_index].m_rows[row_index].m_selected_image)
      return;
//--- Sichern des Index des gewählten Icons der Zelle
   m_columns[column_index].m_rows[row_index].m_selected_image=(int)image_index;
//--- Neuzeichnen der Zelle, wenn angegeben
   if(redraw)
      RedrawCell(column_index,row_index);
      }

Eine weitere Methode wird für das Neuzeichnen der ganzen Tabelle benötigt — CCanvasTable::UpdateTable(). Sie kann auf zweifache Weise aufgerufen werden: 

  1. Wenn die Tabelle einfach nur aktualisiert werden muss, um die letzten Änderungen durch die oben beschriebenen Methoden anzuzeigen.
  2. Wenn die Tabelle komplett neu gezeichnet werden soll, nach vorgenommenen Änderungen.

Standardmäßig steht das einzige Argument der Methode auf false, welches eine Aktualisierung ohne Neuzeichnung bedeutet. 

class CCanvasTable : public CElement
  {
private:
   //--- Aktualisieren der Tabelle
   void              UpdateTable(const bool redraw=false);
  };
//+------------------------------------------------------------------+
//| Aktualisieren der Tabelle                                        |
//+------------------------------------------------------------------+
void CCanvasTable::UpdateTable(const bool redraw=false)
  {
//--- Neuzeichnen der Tabelle, wenn angegeben
   if(redraw)
      DrawTable();
//--- Aktualisieren der Tabelle
   m_table.Update();
      }

Unten sieht man das Ergebnis der bisherigen Arbeit:

 Fig. 3. Demonstration der neuen Eigenschaften der Tabellendarstellung.

Fig. 3. Demonstration der neuen Eigenschaften der Tabellendarstellung.


Ein Expert Advisor, der die besprochenen Eigenschaften demonstriert, ist in der Datei am Ende des Artikels und kann heruntergeladen werden. Solange dieses Programm läuft, werden die Icons in allen Zellen der Tabelle (5 Spalten und 30 Zeilen) alle 100 Millisekunden gewechselt. Der Screenshot unten zeigt die CPU-Belastung, ohne dass eine Nutzer etwas über das grafischen Interface der MQL-Anwendung eingreift. Die CPU-Last übersteigt bei einer Frequenz von 100 Millisekunden niemals den Wert von 3%.

 Fig. 4. Die CPU-Last während der Ausführung der MQL-Testanwendung.

Fig. 4. Die CPU-Last während der Ausführung der MQL-Testanwendung. 

 

 

Anwendung zum Testen der Kontrollelemente

Die aktuelle Version der Tabellendarstellung ist bereits "klug" genug, um Tabellen ähnlich wie zum Beispiel dem Datenfenster (Market Watch) nachzubauen. Versuchen wir das einmal. Wir erstellen zum Beispiel eine Tabelle mit 5 Spalten und 25 Zeilen. Es sind die 25 Symbole des Demo-Servers von MetaQuotes. Folgende Daten erscheinen in der Tabelle:

  • Symbol – Wertpapiere (Währungspaare).
  • Bid – Bid Preis.
  • Ask – Ask Preis.
  • Spread (!) – Differenz zwischen den Preisen Ask und Bid.
  • Time – Zeitpunkt des letzten Datensatzes.

Bereiten wir dieselben Icons vor, die im Datenfenster uns die Richtung der letzten Preisänderungen anzeigen. Die erste Initialisierung der Tabellenzellen geschieht gleich in der Methode, die das Kontrollelement erstellt, und wird durch den Aufruf der Hilfsmethode CProgram :: InitializingTable() der Nutzerklasse ausgeführt. 

//+------------------------------------------------------------------+
//| Klasse für die Erstellung der Anwendung                          |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- Initialisierung der Tabelle
   void              InitializingTable(void);
  };
//+------------------------------------------------------------------+
//| Initialisierung der Tabelle                                      |
//+------------------------------------------------------------------+
void CProgram::InitializingTable(void)
  {
//--- Array der Spaltenköpfe
   string text_headers[COLUMNS1_TOTAL]={"Symbol","Bid","Ask","!","Time"};
//--- Array der Symbole
   string text_array[25]=
     {
      "AUDUSD","GBPUSD","EURUSD","USDCAD","USDCHF","USDJPY","NZDUSD","USDSEK","USDHKD","USDMXN",
      "USDZAR","USDTRY","GBPAUD","AUDCAD","CADCHF","EURAUD","GBPCHF","GBPJPY","NZDJPY","AUDJPY",
      "EURJPY","EURCHF","EURGBP","AUDCHF","CHFJPY"
     };
//--- Array der Icons
   string image_array[3]=
     {
      "::Images\\EasyAndFastGUI\\Icons\\bmp16\\circle_gray.bmp",
      "::Images\\EasyAndFastGUI\\Icons\\bmp16\\arrow_up.bmp",
      "::Images\\EasyAndFastGUI\\Icons\\bmp16\\arrow_down.bmp"
     };
//---
   for(int c=0; c<COLUMNS1_TOTAL; c++)
     {
      //--- Setzen der Spaltenköpfe
      m_canvas_table.SetHeaderText(c,text_headers[c]);
      //---
      for(int r=0; r<ROWS1_TOTAL; r++)
        {
         //--- Setzen der Icons
         m_canvas_table.SetImages(c,r,image_array);
         //--- Setzen der Symbolnamen
         if(c<1)
            m_canvas_table.SetValue(c,r,text_array[r]);
         //--- Standardwerte aller Zellen
         else
            m_canvas_table.SetValue(c,r,"-");
        }
     }
      }

Die Werte der Tabellenzellen werden während der Laufzeit durch den Timer alle 16 Millisekunden aktualisiert. Ein andere Hilfsmethode CProgram::UpdateTable() übernimmt diese Aufgabe. Verlassen wird die Methode am Wochenende (Samstag und Sonntag). Dann iteriert eine Doppelschleife über alle Spalten und Zeilen der Tabelle. Diese Doppelschleife erfragt die letzten beiden Ticks von jedem Symbol und nach der Analyse der Preisänderung, werden die entsprechenden Werte geschrieben. 

class CProgram : public CWndEvents
  {
private:
   //--- Initialisierung der Tabelle
   void              InitializingTable(void);
  };
//+------------------------------------------------------------------+
//| Aktualisieren der Tabellenwerte                                  |
//+------------------------------------------------------------------+
void CProgram::UpdateTable(void)
  {
   MqlDateTime check_time;
   ::TimeToStruct(::TimeTradeServer(),check_time);
//--- Verlassen, wenn Samstag oder Sonntag
   if(check_time.day_of_week==0 || check_time.day_of_week==6)
      return;
//---
   for(int c=0; c<m_canvas_table.ColumnsTotal(); c++)
     {
      for(int r=0; r<m_canvas_table.RowsTotal(); r++)
        {
         //--- Das Symbol der abzufragenden Daten
         string symbol=m_canvas_table.GetValue(0,r);
         //--- Abruf der letzten beiden Ticks
         MqlTick ticks[];
         if(::CopyTicks(symbol,ticks,COPY_TICKS_ALL,0,2)<2)
            continue;
         //--- Deklarieren des Arrays als Zeitreihe
         ::ArraySetAsSeries(ticks,true);
         //--- Symbolspalte - Symbol. Bestimmen der Richtung der Preise.
         if(c==0)
           {
            int index=0;
            //--- Wenn keine Preisänderung
            if(ticks[0].ask==ticks[1].ask && ticks[0].bid==ticks[1].bid)
               index=0;
            //--- Wenn Bid-Preis gestiegen
            else if(ticks[0].bid>ticks[1].bid)
               index=1;
            //--- Wenn Bid-Preis gefallen
            else if(ticks[0].bid<ticks[1].bid)
               index=2;
            //--- Setzen des entsprechenden Icons
            m_canvas_table.ChangeImage(c,r,index,true);
           }
         else
           {
            //--- Spalte der Preisdifferenzen - Spread (!)
            if(c==3)
              {
               //--- Ermitteln des Spread in Points
               int spread=(int)::SymbolInfoInteger(symbol,SYMBOL_SPREAD);
               m_canvas_table.SetValue(c,r,string(spread),true);
               continue;
              }
            //--- Ermitteln der Dezimalstellen
            int digit=(int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS);
            //--- Spalte der Bid-Preise
            if(c==1)
              {
               m_canvas_table.SetValue(c,r,::DoubleToString(ticks[0].bid,digit));
               //--- Farbauswahl bei einer Preisänderung gemäß Änderungsrichtung
               if(ticks[0].bid!=ticks[1].bid)
                  m_canvas_table.TextColor(c,r,(ticks[0].bid<ticks[1].bid)? clrRed : clrBlue,true);
               //---
               continue;
              }
            //--- Spalte der Ask-Preise
            if(c==2)
              {
               m_canvas_table.SetValue(c,r,::DoubleToString(ticks[0].ask,digit));
               //--- Farbauswahl bei einer Preisänderung gemäß Änderungsrichtung
               if(ticks[0].ask!=ticks[1].ask)
                  m_canvas_table.TextColor(c,r,(ticks[0].ask<ticks[1].ask)? clrRed : clrBlue,true);
               //---
               continue;
              }
            //--- Spalte der Zeit der zuletzt erhaltenen Kurse des Symbols
            if(c==4)
              {
               long   time     =::SymbolInfoInteger(symbol,SYMBOL_TIME);
               string time_msc =::IntegerToString(ticks[0].time_msc);
               int    length   =::StringLen(time_msc);
               string msc      =::StringSubstr(time_msc,length-3,3);
               string str      =::TimeToString(time,TIME_MINUTES|TIME_SECONDS)+"."+msc;
               //---
               color clr=clrBlack;
               //--- Wenn keine Preisänderung
               if(ticks[0].ask==ticks[1].ask && ticks[0].bid==ticks[1].bid)
                  clr=clrBlack;
               //--- Wenn Bid-Preis gestiegen
               else if(ticks[0].bid>ticks[1].bid)
                  clr=clrBlue;
               //--- Wenn Bid-Preis gefallen
               else if(ticks[0].bid<ticks[1].bid)
                  clr=clrRed;
               //--- Setzen des Textes und der Textfarbe
               m_canvas_table.SetValue(c,r,str);
               m_canvas_table.TextColor(c,r,clr,true);
               continue;
              }
           }
        }
     }
//--- Aktualisieren der Tabelle
   m_canvas_table.UpdateTable();
      }

Und so sieht das dann aus:

Fig. 5. Datenvergleich zwischen dem Datenfenster und unserer Tabelle.

Fig. 5. Datenvergleich zwischen dem Datenfenster und unserer Tabelle. 


Die Testanwendung dieses Artikels kann mittel des Links unten heruntergeladen werden, für ein weiteres Studium. 

 

Schlussfolgerung

Das Schema der Bibliothek des grafischen Interfaces im aktuellen Entwicklungsstand schaut aus wie unten abgebildet.

Fig. 6. Struktur der Bibliothek im augenblicklichen Entwicklungszustand. 

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