English Русский 中文 Español 日本語 Português
Grafische Interfaces X: Neue Möglichkeiten der Tabellendarstellung (build 9)

Grafische Interfaces X: Neue Möglichkeiten der Tabellendarstellung (build 9)

MetaTrader 5Beispiele | 31 März 2017, 16:32
555 0
Anatoli Kazharski
Anatoli Kazharski

Inhalt


Einführung

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

Bis jetzt war CTable die fortschrittlichste Tabellenart in dieser Bibliothek. Diese Tabelle ist zusammengestellt aus editierbaren Boxen des Typs OBJ_EDIT Typ, aber eine weitere Entwicklung ist problematisch. Es wäre zum Beispiel schwierig, manuell die Spaltenbreite durch das Verschieben der Grenze eines Spaltenkopfes zu ändern, da es unmöglich ist, die sichtbaren Teile einzelner Grafikobjekte der Tabelle zu kontrollieren. Hier wurde die Grenze erreicht.

Beim augenblicklichen Stand der Bibliothek ist daher sinnvoller, die Entwicklung von dargestellten Tabellen des Typs CCanvasTable voranzutreiben. Informationen zu den bisherigen Versionen und Updates dargestellter Tabellen finden Sie hier:

Die Bildschirmfotos zeigen die letzten Versionen dargestellter Tabellen. Wie man sieht, sie sind komplett starr. Es ist eine einfaches Gitter mit Daten. Die Textpositionierung wird je Zelle definiert. Bis auf die Bildlaufleiste und einer automatischen Größenanpassung reagiert die Tabelle nicht auf Größenänderungen.

 Fig. 1. Die vorherigen Version einer Tabellendarstellung.

Fig. 1. Die vorherigen Version einer Tabellendarstellung.

Um das zu ändern, erweitern wir die Tabelle mit neuen Eigenschaften. Folgendes wird durch das aktuelle Update hinzugefügt:

  • Formatierung im Zebra-Stil
  • Aus- und Abwahl von Tabellenspalten durch Mausklicks
  • Spaltenköpfe, die ihre Farbe wechseln, wenn die Maus über ihnen ist und sie geklickt werden
  • Automatisches Anpassen der Textbreite an die Spaltenbreite, wenn es nicht genug Platz gibt
  • Ändern einer Spaltenbreite durch das Verschieben der Spaltengrenze

Formatierung im Zebra-Stil

Die Tabelle CTable des letzten Artikels kann ja bereits im Zebra-Stil angezeigt werden. Das erleichtert die Übersicht von Tabellen mit vielen Zellen. Das werden wir auch für die Tabellendarstellung übernehmen.

Verwenden wir die Methode CCanvasTable::IsZebraFormatRows() dafür. Sie verwendet die zweite Farbe der Tabellenstils, während die Zellen allgemein mit der Ersten gezeichnet werden.

//+------------------------------------------------------------------+
//| Kasse des Erzeugens eine Tabellendarstellung                     |
//+------------------------------------------------------------------+
class CCanvasTable : public CElement
  {
private:
   //--- Formatmodus des Zebra-Stils
   color             m_is_zebra_format_rows;
   //---
public:
   //--- Formatierung im Zebra-Stil
   void              IsZebraFormatRows(const color clr)   { m_is_zebra_format_rows=clr;      }
      };

Die Methode der Darstellung unterscheidet sich von denen anderer Tabellen. CCanvasTable färbt normalerweise den ganzen Hintergrund (Leinwand der Zeichnung) in der allgemeinen Farbe der Zellen. Im Zebra-Modus beginnt eine Schleife. Bei jeder Iteration werden die Koordinaten berechnet und die Bereiche alternierend in eine der beiden Farben gezeichnet. Das wird von der Methode FillRectangle() erledigt, die für das Ausfüllen von Rechtecken verwendet wird

class CCanvasTable : public CElement
  {
public:
   //--- Zeichnen des Hintergrundes der Tabellenzeilen
   void              DrawRows(void);
  };
//+------------------------------------------------------------------+
//| Zeichnen des Hintergrundes der Tabellenzeilen                    |
//+------------------------------------------------------------------+
void CCanvasTable::DrawRows(void)
  {
//--- Wenn der Zebra-Stil deaktiviert ist
   if(m_is_zebra_format_rows==clrNONE)
     {
      //--- Färben der Leinwand
      m_table.Erase(::ColorToARGB(m_cell_color));
      return;
     }
//--- Koordinaten der Spaltenköpfe
   int x1=0,x2=m_table_x_size;
   int y1=0,y2=0;
//--- Formatierung im Zebra-Stil
   for(int r=0; r<m_rows_total; r++)
     {
      //--- Berechnen der Koordinaten
      y1=(r*m_cell_y_size)-r;
      y2=y1+m_cell_y_size;
      //--- Gefärbte Zeile
      uint clr=::ColorToARGB((r%2!=0)? m_is_zebra_format_rows : m_cell_color);
      //--- Zeichnen des Zeilenhintergrundes
      m_table.FillRectangle(x1,y1,x2,y2,clr);
     }
      }

Die Farbe der Zeile wird von Ihnen gewählt. Im Ergebnis schaut die Tabelle dann so aus:

 Fig. 2. Tabellendarstellung im Zebra-Stil.

Fig. 2. Tabellendarstellung im Zebra-Stil. 

 


Auswahl und Abwahl von Tabellenzeilen

Die Zeilenauswahl benötigt zusätzliche Variablen und Methoden:

  • Die Farben des Hintergrundes und des Textes der ausgewählten Zeile
  • Index und Text

class CCanvasTable : public CElement
  {
private:
   //--- Farbe (1) des Hintergrundes und (2) der gewählten Textzeile
   color             m_selected_row_color;
   color             m_selected_row_text_color;
   //--- (1) Index und (2) Text der gewählten Zeile
   int               m_selected_item;
   string            m_selected_item_text;
   //---
public:
   //--- Rückgabe (1) des Index und (2) des Textes der gewählten Tabellenzeile
   int               SelectedItem(void)             const { return(m_selected_item);         }
   string            SelectedItemText(void)         const { return(m_selected_item_text);    }
   //---
private:
   //--- Zeichnen des Hintergrundes der Tabellenzeilen
   void              DrawRows(void);
      };

Die wählbaren Zeilen können mit der Methode CCanvasTable::SelectableRow() aus-/abgewählt werden:

class CCanvasTable : public CElement
  {
private:
   //--- Zeilen sind wählbar
   bool              m_selectable_row;
   //---
public:
   //--- Zeilenauswahl
   void              SelectableRow(const bool flag)       { m_selectable_row=flag;           }
      };

Eine neue Methode wird für das Zeichnen nutzerdefinierter Bereiche benötigt, um eine Zeile auszuwählen. Der Code dieser Methode CCanvasTable::DrawSelectedRow() folgt unten. Sie berechnet die Koordinaten des gewählten Bereiches der Leinwand, der dann als Rechteck ausgemalt wird

class CCanvasTable : public CElement
  {
private:
   //--- Zeichnen der gewählten Zeile
   void              DrawSelectedRow(void);
  };
//+------------------------------------------------------------------+
//| Zeichnen der gewählten Zeile                                     |
//+------------------------------------------------------------------+
void CCanvasTable::DrawSelectedRow(void)
  {
//--- Setze die ersten Koordinaten für die Überprüfungen
   int y_offset=(m_selected_item*m_cell_y_size)-m_selected_item;
//--- Koordinaten
   int x1=0,x2=0,y1=0,y2=0;
//---
   x1=0;
   y1=y_offset;
   x2=m_table_x_size;
   y2=y_offset+m_cell_y_size-1;
//--- Zeichnen eines ausgemalten Rechtecks
   m_table.FillRectangle(x1,y1,x2,y2,::ColorToARGB(m_selected_row_color));
      }

Die Hilfsmethode CCanvasTable::TextColor() wird benötigt, um den Text neu zu zeichnen, das wiederum die Textfarbe der Zelle bestimmt: 

class CCanvasTable : public CElement
  {
private:
   //--- Rückgabe der Farbe des Textes der Zelle
   uint              TextColor(const int row_index);
  };
//+------------------------------------------------------------------+
//| Rückgabe der Farbe des Textes der Zelle                          |
//+------------------------------------------------------------------+
uint CCanvasTable::TextColor(const int row_index)
  {
   uint clr=::ColorToARGB((row_index==m_selected_item)? m_selected_row_text_color : m_cell_text_color);
//--- Rückgabe der Farbe des Spaltenkopfes
   return(clr);
      }

Um eine Tabellenzeile auszuwählen, muss sie doppelt geklickt werden Das verlangt nach der Methode CCanvasTable::OnClickTable(), die über den Identifikator CHARTEVENT_OBJECT_CLICK der Ereignisbehandlung des Steuerelementes aufgerufen wird.

Mehrere Dinge müssen zu Beginn der Methode überprüft werden. Das Programm verlässt die Methode, wenn:

  • Zeilenauswahl deaktiviert ist;
  • die Bildlaufleiste aktiv ist;
  • Ein Klick außerhalb der Tabelle erfolgte. 

Nach bestandenen Prüfungen werden die Koordinaten des Klicks berechnet, dafür wird der aktuelle Abstand zu den Kanten der Hintergrundes und der Y-Koordinate des Mauskursor benötigt. Danach wird die geklickte Zeile in einer Schleife bestimmt. Wurde die Zeile gefunden, muss überprüft werden, ob es die aktuell ausgewählte ist, und wenn ja — deaktivieren. Wurde die Zeile ausgewählt, muss ihr Index und der Text vom Beginn der Spalte gesichert werden. Nach der Beendigung der Schleife zur Suche der Zeile wird die Tabelle neu gezeichnet. Es wird eine Nachricht folgenden Inhalts verschickt:

  • Identifikator des Ereignisses ON_CLICK_LIST_ITEM
  • Identifikator des Steuerelementes
  • Index der gewählten Zeile
  • Text der gewählten Zeile. 
class CCanvasTable : public CElement
  {
private:
   //--- Handhabung eines festgehaltenen Elementes
   bool              OnClickTable(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Handhabung eines Klicks auf das Textfeld                         |
//+------------------------------------------------------------------+
bool CCanvasTable::OnClickTable(const string clicked_object)
  {
//--- Verlassen, wenn der Auswahlmodus nicht aktiv ist
   if(!m_selectable_row)
      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);
//--- Abfrage des Abstandes der X- und Y-Achse
   int xoffset=(int)m_table.GetInteger(OBJPROP_XOFFSET);
   int yoffset=(int)m_table.GetInteger(OBJPROP_YOFFSET);
//--- Bestimme die Koordinaten des Textfeldes unter dem Mauskursor
   int y=m_mouse.Y()-m_table.Y()+yoffset;
//--- Bestimmen der geklickten Zeile
   for(int r=0; r<m_rows_total; r++)
     {
      //--- Setze die ersten Koordinaten für die Überprüfungen
      int y_offset=(r*m_cell_y_size)-r;
      //--- Zustandsprüfung bezüglich der Y-Achse
      bool y_pos_check=(y>=y_offset && y<y_offset+m_cell_y_size);
      //--- Wenn der Klick nicht in dieser Zeile erfolgte, weiter
      if(!y_pos_check)
         continue;
      //--- Abwählen, wenn die ausgewählte Zeile geklickt wurde
      if(r==m_selected_item)
        {
         m_selected_item      =WRONG_VALUE;
         m_selected_item_text ="";
         break;
        }
      //--- Sichern des Zeilenindex 
      m_selected_item      =r;
      m_selected_item_text =m_vcolumns[0].m_vrows[r];
      break;
     }
//--- Zeichnen der Tabelle
   DrawTable();
//--- Senden einer Nachricht darüber
   ::EventChartCustom(m_chart_id,ON_CLICK_LIST_ITEM,CElementBase::Id(),m_selected_item,m_selected_item_text);
   return(true);
      }

Eine Tabellendarstellung mit einer ausgewählten Zeile sieht so aus:

Fig. 3. Demonstration der Aus- und Abwahl einer Zeile in der Tabellendarstellung.

Fig. 3. Demonstration der Aus- und Abwahl einer Zeile in der Tabellendarstellung. 

 

Spaltenköpfe

Jede Tabelle ohne Spaltenköpfe sieht aus wie ein leeres Blatt. Die Spaltenköpfe werde in der gleichen Art wie die Tabelle gezeichnet, aber vor einem anderen Hintergrund. Dafür wird eine weitere Instanz der Klasse CRectCanvas der Klasse CCanvasTable hinzugefügt und eine eigene Methode für den Hintergrund geschrieben. Der Code dieser Methode wird hier nicht weiter erwähnt: Er ist praktisch ident mit dem für das Erstellen einer Tabelle. Der einzige Unterschied ist vorgegebenen Größen und die Lage des Objektes.

class CCanvasTable : public CElement
  {
private:
   //--- Objekte für das Erstellen einer Tabelle
   CRectCanvas       m_headers;
   //---
private:
   bool              CreateHeaders(void);
      };

Betrachten wir nun die Eigenschaften der Spaltenköpfe. Sie können vor dem Erstellen der Tabelle konfiguriert werden.

  • Anzeigeart der Spaltenköpfe der Tabelle.
  • Größe (Höhe) der Spaltenköpfe.
  • Hintergrundfarbe der Spaltenköpfe in verschiedenen Zuständen.
  • Textfarbe der Spaltenköpfe.

Variablen und Methoden bezüglich dieser Eigenschaften: 

class CCanvasTable : public CElement
  {
private:
   //--- Anzeigeart der Spaltenköpfe der Tabelle
   bool              m_show_headers;
   //--- Größe (Höhe) der Spaltenköpfe
   int               m_header_y_size;
   //--- Hintergrundfarbe der Spaltenköpfe in verschiedenen Zuständen
   color             m_headers_color;
   color             m_headers_color_hover;
   color             m_headers_color_pressed;
   //--- Textfarbe der Spaltenköpfe
   color             m_headers_text_color;
   //---
public:
   //--- (1) Anzeigeart der Spaltenköpfe, (2) Höhe der Spaltenköpfe
   void              ShowHeaders(const bool flag)         { m_show_headers=flag;             }
   void              HeaderYSize(const int y_size)        { m_header_y_size=y_size;          }
   //--- (1) Hintergrund- und (2) Textfarbe der Spaltenköpfe
   void              HeadersColor(const color clr)        { m_headers_color=clr;             }
   void              HeadersColorHover(const color clr)   { m_headers_color_hover=clr;       }
   void              HeadersColorPressed(const color clr) { m_headers_color_pressed=clr;     }
   void              HeadersTextColor(const color clr)    { m_headers_text_color=clr;        }
      };

Es wird eine Methode zur Bestimmung der Namen der Spaltenköpfe benötigt. Zusätzlich auch ein Array zur Sicherung dieser Werte. Die Arraygröße ist gleich der Spaltenzahl und wird in der gleichen Methode CCanvasTable::TableSize() beim Bestimmen der Tabellengröße gesetzt. 

class CCanvasTable : public CElement
  {
private:
   //--- Test der Spaltenköpfe
   string            m_header_text[];
   //---
public:
   //--- Kopieren der Texte in die jew, Spaltenköpfe
   void              SetHeaderText(const int column_index,const string value);
  };
//+------------------------------------------------------------------+
//| Ausfüllen des Arrays der Spaltenköpfe mit dem jew. Index         |
//+------------------------------------------------------------------+
void CCanvasTable::SetHeaderText(const uint column_index,const string value)
  {
//--- Prüfung der Einhaltung der Spaltengrenzen
   uint csize=::ArraySize(m_vcolumns);
   if(csize<1 || column_index>=csize)
      return;
//--- Sichern der Werte im Array
   m_header_text[column_index]=value;
      }

Die Textausrichtung innerhalb der Zellen und Spaltenköpfe verwenden beide die Methode CCanvasTable::TextAlign(). Die Methode zur Ausrichtung gemäß der X-Achse in den Tabellenzellen stimmt überein mit der der Spaltenköpfe , und die Ausrichtung gemäß der Y-Achse wird durch einen übergebenen Wert bestimmt. In dieser Version wird der Text der Spaltenköpfe gemäß der Y-Achse mittig positioniert — TA_VCENTER, und die Zellen erhalten einen Abstand von der Oberkante der Zelle — TA_TOP

class CCanvasTable : public CElement
  {
private:
   //--- Rückgabe der Ausrichtungsart der jew. Spalte
   uint              TextAlign(const int column_index,const uint anchor);
  };
//+------------------------------------------------------------------+
//| Rückgabe der Ausrichtungsart der jew. Spalte                     |
//+------------------------------------------------------------------+
uint CCanvasTable::TextAlign(const int column_index,const uint anchor)
  {
   uint text_align=0;
//--- Ausrichtungsart der jew. Spalte
   switch(m_vcolumns[column_index].m_text_align)
     {
      case ALIGN_CENTER :
         text_align=TA_CENTER|anchor;
         break;
      case ALIGN_RIGHT :
         text_align=TA_RIGHT|anchor;
         break;
      case ALIGN_LEFT :
         text_align=TA_LEFT|anchor;
         break;
     }
//--- Rückgabe der Ausrichtungsart
   return(text_align);
      }

In vielen Tabellen und Betriebssystemen ändert sich der Pointer, wenn er sich über den Spaltengrenzen befindet. Die Abbildung zeigt dies am Beispiel einer Tabelle des Fensters der Toolbox des MetaTrader 5 Handelsterminals. Wenn man jetzt diesen neuen Kursor klickt, verändert er den Änderungsmodus der Spaltenbreite. Die Hintergrundfarbe dieses Spaltenkopfes ändert sich auch.

Fig. 4. Mauszeiger über der Grenze eines Spaltenkopfes.

Fig. 4. Mauszeiger über der Grenze eines Spaltenkopfes.

 

Erstellen wir ein gleiches Verhalten mit der Bibliothek. Im Anhang am Ende des Artikels gibt es ein Verzeichnis mit allen Bildern. Wir fügen neue Identifikatoren der Enumeration ENUM_MOUSE_POINTER von Zeigern in der Datei Enums.mqh hinzu, um eine Anpassung bezüglich der X- und Y-Achse zu ermöglichen: 

//+------------------------------------------------------------------+
//| Enumeration der Zeigerarten                                      |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_POINTER
  {
   MP_CUSTOM            =0,
   MP_X_RESIZE          =1,
   MP_Y_RESIZE          =2,
   MP_XY1_RESIZE        =3,
   MP_XY2_RESIZE        =4,
   MP_X_RESIZE_RELATIVE =5,
   MP_Y_RESIZE_RELATIVE =6,
   MP_X_SCROLL          =7,
   MP_Y_SCROLL          =8,
   MP_TEXT_SELECT       =9
      };

Es müssen geeignete Erweiterungen der Klasse CPointer vorgenommen werden, um die Verwendung dieser Zeigerarten durch die Kontrollklasse zu ermöglichen. 

//+------------------------------------------------------------------+
//|                                                      Pointer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//--- Resources
#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_rs_rel.bmp"
#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_y_rs_rel.bmp"
//+------------------------------------------------------------------+
//| Klasse für die Erzeugung eines Mauszeigers                       |
//+------------------------------------------------------------------+
class CPointer : public CElement
  {
private:
   //--- Festlegen des Bildes des Mauszeigers
   void              SetPointerBmp(void);
  };
//+------------------------------------------------------------------+
//| Festlegen des Zeigersymbols gemäß der Zeigerart                  |
//+------------------------------------------------------------------+
void CPointer::SetPointerBmp(void)
  {
   switch(m_type)
     {
      ...
      case MP_X_RESIZE_RELATIVE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs_rel.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs_rel.bmp";
         break;
      case MP_Y_RESIZE_RELATIVE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs_rel.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs_rel.bmp";
         break;
      ...
     }
//--- Wenn ein nutzerdefinierte Typ (MP_CUSTOM) angegeben wurde
   if(m_file_on=="" || m_file_off=="")
      ::Print(__FUNCTION__," > Both images must be set for the cursor!");
      }

Weiter Variablen werden jetzt noch benötigt:

  • Um zu bestimmen, wenn die Grenzen eines Spaltenkopfes verändert wird
  • Um festzustellen, wenn der Mauszeiger eine Grenze zwischen zwei Spalten überschreitet. So können wir die Belastung reduzieren, da nur dann die die Texte neu geschrieben werden, wenn die benachbarte Region berührt wird. 

class CCanvasTable : public CElement
  {
private:
   //--- Zur Bestimmung des Augenblicks, da der Mauskursor sich den Rahmen des Textfeldes kreuzt
   int               m_prev_header_index_focus;
   //--- Zustand der Änderung der der Breite eines Spaltenkopfes
   int               m_column_resize_control;
      };

Die Methode CCanvasTable::HeaderColorCurrent() fragt die aktuelle Farbe des Spaltenkopfes ab, abhängig vom Modus, der Position des Mauszeigers und der linken Maustaste. Der Fokus des Spaltenkopfes wird bestimmt durch die Methode CCanvasTable::DrawHeaders(), die den Hintergrund des Spaltenkopfes ausfüllt und der Prüfergebnis übergeben wird.

class CCanvasTable : public CElement
  {
private:
   //--- Rückgabe der der aktuellen Hintergrundfarbe des Spaltenkopfes
   uint              HeaderColorCurrent(const bool is_header_focus);
  };
//+------------------------------------------------------------------+
//| Rückgabe der der aktuellen Hintergrundfarbe des Spaltenkopfes    |
//+------------------------------------------------------------------+
uint CCanvasTable::HeaderColorCurrent(const bool is_header_focus)
  {
   uint clr=clrNONE;
//--- Wenn nicht im Fokus
   if(!is_header_focus || !m_headers.MouseFocus())
      clr=m_headers_color;
   else
     {
      //--- Wenn die linke Maustaste gedrückt wurde und ohne dass zugleich die Spaltenbreite verändert wurde
      bool condition=(m_mouse.LeftButtonState() && m_column_resize_control==WRONG_VALUE);
      clr=(condition)? m_headers_color_pressed : m_headers_color_hover;
     }
//--- Rückgabe der Farbe des Spaltenkopfes
   return(::ColorToARGB(clr));
      }

Der Code der Methode CCanvasTable::DrawHeaders() ist unten aufgeführt. Hier, wenn die Maus sich nicht im Bereich eines Spaltenkopfes befindet, wird der gesamte Hintergrund mit der angebenden Farbe ausgemalt. Stehen Spaltenköpfe im Fokus, dann muss abgefragt werden, welcher im Fokus steht. Dafür müssen die relativen Koordinaten des Mauskursors und, in einer Schleife, die Spaltenköpfe, die im Fokus stehen, bestimmt werden. Zusätzlich wird hier auch der Modus der Änderung der Spaltenbreite behandelt. Dafür wird ein weiterer Abstand für die Berechnung in diesem Modus verwendet. Wurde der Fokus gefunden, muss der Spaltenindex gesichert werden

class CCanvasTable : public CElement
  {
private:
   //--- Abstand zu den Kanten der Trennungslinie, um dem Mauszeiger in der Form für die Änderung der Spaltenbreite zu zeigen
   int               m_sep_x_offset;
   //---
private:
   //--- Zeichnen der Spaltenköpfe
   void              DrawHeaders(void);
  };
//+------------------------------------------------------------------+
//| Zeichnen der Spaltenköpfe                                        |
//+------------------------------------------------------------------+
void CCanvasTable::DrawHeaders(void)
  {
//--- Wenn nicht im Fokus, wiederherstellen der Farben des Spaltenkopfes
   if(!m_headers.MouseFocus())
     {
      m_headers.Erase(::ColorToARGB(m_headers_color));
      return;
     }
//--- Prüfen des Fokus auf die Spaltenköpfe
   bool is_header_focus=false;
//--- Koordinaten des Mauskursors
   int x=0;
//--- Koordinaten
   int x1=0,x2=0,y1=0,y2=m_header_y_size;
//--- Abfrage der relativen Koordinaten des Mauskursors
   if(::CheckPointer(m_mouse)!=POINTER_INVALID)
     {
      //--- Abfrage des Abstandes zur X-Achse
      int xoffset=(int)m_headers.GetInteger(OBJPROP_XOFFSET);
      //--- Koordinatenbestimmung des Mauskursors
      x=m_mouse.X()-m_headers.X()+xoffset;
     }
//--- Löschen des Hintergrundes der Spaltenköpfe
   m_headers.Erase(::ColorToARGB(clrNONE,0));
//--- Abstand unter Berücksichtigung des Änderungsmodus der Spaltenbreite
   int sep_x_offset=(m_column_resize_mode)? m_sep_x_offset : 0;
//--- Zeichnen des Hintergrundes der Spaltenköpfe
   for(int i=0; i<m_columns_total; i++)
     {
      //--- Berechnen der Koordinaten
      x2+=m_vcolumns[i].m_width;
      //--- Prüfen des Fokus
      if(is_header_focus=x>x1+((i!=0)? sep_x_offset : 0) && x<=x2+sep_x_offset)
         m_prev_header_index_focus=i;
      //--- Zeichnen des Hintergrundes der Spaltenköpfe
      m_headers.FillRectangle(x1,y1,x2,y2,HeaderColorCurrent(is_header_focus));
      //--- Berechnen des Abstands für den nächsten Spaltenkopf
      x1+=m_vcolumns[i].m_width;
     }
      }

Ist jetzt der Hintergrund der Spaltenköpfe ausgefüllt, ist es notwendig das Gitter (Begrenzungslinien) zu zeichnen. Die Methode CCanvasTable::DrawHeadersGrid() wird dafür verwendet. Zuerst wird der gemeinsame Rahmen gezeichnet, und dann, in einer Schleife, die Trennungslinien.

class CCanvasTable : public CElement
  {
private:
   //--- Zeichnen des Gitters der Spaltenköpfe
   void              DrawHeadersGrid(void);
  };
//+------------------------------------------------------------------+
//| Zeichnen des Gitters der Spaltenköpfe                             |
//+------------------------------------------------------------------+
void CCanvasTable::DrawHeadersGrid(void)
  {
//--- Gitterfarbe
   uint clr=::ColorToARGB(m_grid_color);
//--- Koordinaten
   int x1=0,x2=0,y1=0,y2=0;
   x2=m_table_x_size-1;
   y2=m_header_y_size-1;
//--- Zeichnen des Rahmens
   m_headers.Rectangle(x1,y1,x2,y2,clr);
//--- Trennungslinie
   x2=x1=m_vcolumns[0].m_width;
   for(int i=1; i<m_columns_total; i++)
     {
      m_headers.Line(x1,y1,x2,y2,clr);
      x2=x1+=m_vcolumns[i].m_width;
     }
      }

Und zuletzt, das Zeichnen der Texte der Spaltenköpfe. Diese Aufgabe bewältigt die Methode CCanvasTable::DrawHeadersText(). Hier muss in einer Schleife über alle Spaltenköpfe iteriert werden, um die Koordinaten des Textes zu bestimmen und die Ausrichtung bei jedem Durchlauf. Der Name des Spaltenkopfes wird als letzte Operation in der Schleife geschrieben. Auch die Textanpassung relativ zur Spaltenbreite findet hier statt. Die Methode CCanvasTable::CorrectingText() wird hierzu verwendet. Im nächsten Kapitel wird sie detailliert beschrieben. 

class CCanvasTable : public CElement
  {
private:
   //--- Zeichnen der Texte der Spaltenköpfe
   void              DrawHeadersText(void);
  };
//+------------------------------------------------------------------+
//| Zeichnen der Texte der Spaltenköpfe                              |
//+------------------------------------------------------------------+
void CCanvasTable::DrawHeadersText(void)
  {
//--- Berechnen der Koordinaten und Abstände
   int x=0,y=m_header_y_size/2;
   int column_offset =0;
   uint text_align   =0;
//--- Textfarbe
   uint clr=::ColorToARGB(m_headers_text_color);
//--- Eigenschaften des Schrifttyps
   m_headers.FontSet(CElementBase::Font(),-CElementBase::FontSize()*10,FW_NORMAL);
//--- Textausgabe
   for(int c=0; c<m_columns_total; c++)
     {
      //--- Abfragen der X-Koordinate des Textes
      x=TextX(c,column_offset);
      //--- Abfrage der Textausrichtung
      text_align=TextAlign(c,TA_VCENTER);
      //--- Zeichnen der Spaltennamen
      m_headers.TextOut(x,y,CorrectingText(c,0,true),clr,text_align);
     }
      }

Alle aufgelisteten Methoden des Zeichnens werden von der Methode CCanvasTable::DrawTableHeaders() aufgerufen. Die Ausführung wird blockiert, wenn die Anzeige der Spaltenköpfe nicht aktiviert ist

class CCanvasTable : public CElement
  {
private:
   //--- Zeichnen der Spaltenköpfe
   void              DrawTableHeaders(void);
  };
//+------------------------------------------------------------------+
//| Zeichnen der Spaltenköpfe                                        |
//+------------------------------------------------------------------+
void CCanvasTable::DrawTableHeaders(void)
  {
//--- Verlassen, wenn die Spaltenköpfe deaktiviert sind
   if(!m_show_headers)
      return;
//--- Zeichnen der Spaltenköpfe
   DrawHeaders();
//--- Zeichnen des Gitters
   DrawHeadersGrid();
//--- Zeichnen des Textes der Spaltenköpfe
   DrawHeadersText();
      }

Der Fokus auf einen Spaltenkopf wird von der Methode CCanvasTable::CheckHeaderFocus() überprüft. Das Programm verlässt die Methode in zwei Fällen:

  • wenn der Anzeige der Spaltenköpfe nicht aktiviert ist
  • oder wenn die Breitenänderung einer Spalte begonnen hat.

Danach werden die relativen Koordinaten des Kursors auf dem Hintergrund abgefragt. Die Schleife sucht den Fokus eines Spaltenkopfes und prüft, ob er sich seit dem letzten Aufruf dieser Methode geändert hat. Wird ein neuer Fokus erkannt (der Moment des Kreuzens der Spaltengrenzen), dann ist es notwendig den vorher gesicherten Index des Spaltenkopfes zurückzusetzen und die Schleife zu stoppen.

class CCanvasTable : public CElement
  {
private:
   //--- Prüfen des Fokus auf einen Spaltenkopf
   void              CheckHeaderFocus(void);
  };
//+------------------------------------------------------------------+
//| Prüfen des Fokus auf einen Spaltenkopf                           |
//+------------------------------------------------------------------+
void CCanvasTable::CheckHeaderFocus(void)
  {
//--- Verlassen, wenn (1) der Spaltenkopf deaktiviert ist oder (2) die Änderung der Spaltenbreite begonnen hat
   if(!m_show_headers || m_column_resize_control!=WRONG_VALUE)
      return;
//--- Koordinaten der Spaltenköpfe
   int x1=0,x2=0;
//--- Abfrage des Abstandes zur X-Achse
   int xoffset=(int)m_headers.GetInteger(OBJPROP_XOFFSET);
//--- Abfrage der relativen Koordinaten des Mauskursors
   int x=m_mouse.X()-m_headers.X()+xoffset;
//--- Abstand unter Berücksichtigung des Änderungsmodus der Spaltenbreite
   int sep_x_offset=(m_column_resize_mode)? m_sep_x_offset : 0;
//--- Suchen des Fokus
   for(int i=0; i<m_columns_total; i++)
     {
      //--- Berechnen der rechten Koordinate
      x2+=m_vcolumns[i].m_width;
      //--- Wenn der Fokus auf den Spaltenkopf geändert wurde
      if((x>x1+sep_x_offset && x<=x2+sep_x_offset) && m_prev_header_index_focus!=i)
        {
         m_prev_header_index_focus=WRONG_VALUE;
         break;
        }
      //--- Berechnen der linken Koordinate
      x1+=m_vcolumns[i].m_width;
     }
      }

Die Spaltenköpfe werden bei jedem Lauf nur dann neu gezeichnet, wenn die Grenzen gekreuzt wurden. Das verringert die CPU-Last. Die Methode CCanvasTable::ChangeHeadersColor() bewältigt diese Aufgabe. Die Methode wird verlassen, wenn die Anzeige der Spaltenköpfe deaktiviert ist oder die Spaltenbreiten verschoben werden. Wurden diese Prüfungen zu Beginn der Methode bestanden, dann wird der Fokus auf die Spaltenköpfe überprüft und diese werden neu gezeichnet. 

class CCanvasTable : public CElement
  {
private:
   //--- Ändern der Farbe der Spaltenköpfe
   void              ChangeHeadersColor(void);
  };
//+------------------------------------------------------------------+
//| Ändern der Farbe der Spaltenköpfe                                |
//+------------------------------------------------------------------+
void CCanvasTable::ChangeHeadersColor(void)
  {
//--- Verlassen, wenn die Spaltenköpfe deaktiviert sind
   if(!m_show_headers)
      return;
//--- Wenn der Kursor aktiviert ist
   if(m_column_resize.IsVisible() && m_mouse.LeftButtonState())
     {
      //--- Sichern des Index der veränderten Spalte
      if(m_column_resize_control==WRONG_VALUE)
         m_column_resize_control=m_prev_header_index_focus;
      //---
      return;
     }
//--- Falls nicht im Fokus
   if(!m_headers.MouseFocus())
     {
      //--- Falls noch nicht bekannt, dass es noch nicht im Fokus ist
      if(m_prev_header_index_focus!=WRONG_VALUE)
        {
         //--- Rücksetzen des Fokus
         m_prev_header_index_focus=WRONG_VALUE;
         //--- Ändern der Farbe
         DrawTableHeaders();
         m_headers.Update();
        }
     }
//--- Wenn im Fokus
   else
     {
      //--- Prüfen des Fokus auf die Spaltenköpfe
      CheckHeaderFocus();
      //--- Wenn nicht im Fokus
      if(m_prev_header_index_focus==WRONG_VALUE)
        {
         //--- Ändern der Farbe
         DrawTableHeaders();
         m_headers.Update();
        }
     }
      }

Unten ist der Code der Methode CCanvasTable::CheckColumnResizeFocus(). Sie wird für die Bestimmung des Fokus auf die Grenzen zwischen den Spaltenköpfen benötigt, und sie ist verantwortlich den Kursor bei der Änderung der Spaltenbreite zu zeigen/verbergen. Es gibt zwei Prüfungen am Anfang der Methode. Die Methode wird verlassen, wenn die Änderung der Spaltenbreite deaktiviert ist. Ist sie aktiviert und die Spaltenbreite wird gerade verändert, dann ist es notwendig, die Koordinaten des Mauskursors zu aktualisieren und die Methode zu verlassen.

Wurde nicht begonnen die Spaltenbreite zu ändern, dann, wenn der Kursor im Bereich des Spaltenkopfes ist, wird versucht den Fokus auf eine der Grenzen zu richten, in einer Schleife. Wurde der Fokus gefunden, werden die Koordinaten des Mauskursors aktualisiert, sichtbar gemacht und die Methode verlassen. Wurde der Fokus nicht gefunden, dann wird der Zeiger ausgeblendet

class CCanvasTable : public CElement
  {
private:
   //--- Prüfen des Fokus auf die Grenzen der Spaltenköpfe, um ihre Breite zu ändern
   void              CheckColumnResizeFocus(void);
  };
//+------------------------------------------------------------------+
//| Prüfen des Fokus auf Spaltengrenzen, um ihre Breite zu ändern    |
//+------------------------------------------------------------------+
void CCanvasTable::CheckColumnResizeFocus(void)
  {
//--- Verlassen, wenn der Modus die Spaltenbreite zu ändern deaktiviert ist
   if(!m_column_resize_mode)
      return;
//--- Verlassen, wenn die Änderung der Spaltenbreite begonnen hat
   if(m_column_resize_control!=WRONG_VALUE)
     {
      //--- Aktualisieren der Koordinaten des Kursors und ihn sichtbar machen
      m_column_resize.Moving(m_mouse.X(),m_mouse.Y());
      return;
     }
//--- Prüfen des Fokus auf die Grenzen der Spaltenköpfe
   bool is_focus=false;
//--- Wenn der Mauskursor im Bereich der Spaltenköpfe ist
   if(m_headers.MouseFocus())
     {
      //--- Koordinaten der Spaltenköpfe
      int x1=0,x2=0;
      //--- Abfrage des Abstandes zur X-Achse
      int xoffset=(int)m_headers.GetInteger(OBJPROP_XOFFSET);
      //--- Abfrage der relativen Koordinaten des Mauskursors
      int x=m_mouse.X()-m_headers.X()+xoffset;
      //--- Suchen des Fokus
      for(int i=0; i<m_columns_total; i++)
        {
         //--- Berechnen der Koordinaten
         x1=x2+=m_vcolumns[i].m_width;
         //--- Bestätigen des Fokus
         if(is_focus=x>x1-m_sep_x_offset && x<=x2+m_sep_x_offset)
            break;
        }
      //--- Gibt es einen Fokus
      if(is_focus)
        {
         //--- Aktualisieren der Koordinaten des Kursors und ihn sichtbar machen
         m_column_resize.Moving(m_mouse.X(),m_mouse.Y());
         //--- Anzeigen des Kursors
         m_column_resize.Show();
         return;
        }
     }
//--- Ausblenden des Zeigers, wenn nicht im Fokus
   if(!m_headers.MouseFocus() || !is_focus)
      m_column_resize.Hide();
      }

Das Endergebnis sieht wie folgt aus:

 Fig. 5. Spaltenköpfe der Spalten.

Fig. 5. Spaltenköpfe der Spalten.

 

 


Anpassung der Länge einer Zeichenkette relativ zur Spaltenbreite

Damit der Text nicht in eine benachbarte Zelle ragt, war es früher notwendig, um das zu erreichen, eine Spaltenbreite manuell zu verändern und neu zu kompilieren. Das ist natürlich unbequem.

Also passen wir jetzt die Textlänge automatisch der Zelle an. Der vorher angepasste Text wird nicht noch einmal angepasst, wenn die Tabelle neu gezeichnet wird. Ergänzen wir die Struktur mit den Tabelleneigenschaften um einen Array zur Textsicherung.

class CCanvasTable : public CElement
  {
private:
   //--- Array der Werte und Eigenschaften der Tabelle
   struct CTOptions
     {
      string            m_vrows[];
      string            m_text[];
      int               m_width;
      ENUM_ALIGN_MODE   m_text_align;
     };
   CTOptions         m_vcolumns[];
      };

Im Ergebnis wird m_vrows[] den ganzen Text aufnehmen, während der Array m_text[] die angepasste Version des Textes enthält.

Die Methode CCanvasTable::CorrectingText() ist verantwortlich für die Anpassung der Länge der Texte sowohl im Spaltenkopf wie in den Zellen. Nach der Identifikation des relevanten Textes , wird die Breite abgefragt. Danach wird geprüft, ob der ganze Text in die Zelle passt, unter Berücksichtigung der Abstände zu den Kanten der Zelle. Passt es, wird der Text im Array m_text[] gesichert und die Methode verlassen. In der aktuellen Version wird der angepasste Text der Zellen gesichert, nicht der der Spaltenköpfe.

Passt der Text nicht, dann müssen die überstehenden Zeichen gekürzt und ein ('…'), eine Ellipse, ergänzt werden. Die Ellipse symbolisiert, dass der angezeigte Text gekürzt wurde. Dieses Vorgehen ist einfach umzusetzen:

1) Abfragen der Textlänge.

2) Dann Iterieren über alle Zeichen in einer Schleife, beginnend mit dem letzten Zeichen, um das letzte Zeichen zu löschen und in einer Hilfsvariablen zwischenzuspeichern.

Ist kein Zeichen mehr übrig, wird eine leere Zeichenkette zurückgegeben.

4) Solange es noch Zeichen gibt, frage die Breite des aktuellen Textes ab, inklusive der Ellipse.

5) Prüfen, ob der Text jetzt in die Zelle passt, unter Berücksichtigung der individuellen Abstände zu den Kanten der Zelle.

6) Passt alles, sichern in einer lokalen Variablen der Methode und verlassen der Schleife.

7) Danach sichern des angepassten Textes im Array m_text[] und die Methode verlassen. 

class CCanvasTable : public CElement
  {
private:
   //--- Rückgabe des der Spaltenbreite angepassten Textes
   string            CorrectingText(const int column_index,const int row_index,const bool headers=false);
  };
//+------------------------------------------------------------------+
//| Rückgabe des der Spaltenbreite angepassten Textes                |
//+------------------------------------------------------------------+
string CCanvasTable::CorrectingText(const int column_index,const int row_index,const bool headers=false)
  {
//--- Holen des aktuellen Textes
   string corrected_text=(headers)? m_header_text[column_index]: m_vcolumns[column_index].m_vrows[row_index];
//--- Abfrage des Abstandes zur Zellkante entlang der X-Achse
   int x_offset=m_text_x_offset*2;
//--- Holen des Pointers auf das Hintergrund-Objekt
   CRectCanvas *obj=(headers)? ::GetPointer(m_headers) : ::GetPointer(m_table);
//--- Abfrage der Textbreite
   int full_text_width=obj.TextWidth(corrected_text);
//--- Passt es in die Zelle, wird der angepassten Text im eigenen Array gesichert und zurückgegeben
   if(full_text_width<=m_vcolumns[column_index].m_width-x_offset)
     {
      //--- Sind es keine Spaltenköpfe, sichern des angepassten Textes
      if(!headers)
         m_vcolumns[column_index].m_text[row_index]=corrected_text;
      //---
      return(corrected_text);
     }
//--- Passt der Text nicht in die Zelle, muss er angepasst werden (kürzen der überstehenden Zeichen und ergänzen der Ellipse)
   else
     {
      //--- Für die Arbeit mit einer Zeichenkette
      string temp_text="";
      //--- Abfrage der Textlänge
      int total=::StringLen(corrected_text);
      //--- Löschen der Zeichen vom Ende her, bis die Textbreite passt
      for(int i=total-1; i>=0; i--)
        {
         //--- Löschen eines Zeichens
         temp_text=::StringSubstr(corrected_text,0,i);
         //--- Nicht übrig, dann sichern einer leeren Zeichenkette
         if(temp_text=="")
           {
            corrected_text="";
            break;
           }
         //--- Ergänze die Ellipse vor der Prüfung
         int text_width=obj.TextWidth(temp_text+"...");
         //--- Wenn es in die Zelle passt
         if(text_width<m_vcolumns[column_index].m_width-x_offset)
           {
            //--- Sichern des Textes und Verlassen der Schleife
            corrected_text=temp_text+"...";
            break;
           }
        }
     }
//--- Sind es keine Spaltenköpfe, sichern des angepassten Textes
   if(!headers)
      m_vcolumns[column_index].m_text[row_index]=corrected_text;
//--- Rückgabe des angepassten Textes
   return(corrected_text);
      }

Die Verwendung angepasster Zeichenketten während des Neuzeichnens von Tabellen, ist besonders beim Ändern der Spaltenbreite wichtig. Aber statt den Text aller Zellen der Tabelle zu ändern, genügt es, nur die Zellen der Spalte zu ändern, die gerade geändert wird. Das verringert die CPU-Last.

Die Methode CCanvasTable::Text() prüft, ob es notwendig ist, den Text einer bestimmten Spalte zu ändern oder, ob es genügt, den vorher angepassten Text zurückzugeben. Ihr Code schaut folgendermaßen aus: 
class CCanvasTable : public CElement
  {
private:
   //--- Rückgabe des Textes
   string            Text(const int column_index,const int row_index);
  };
//+------------------------------------------------------------------+
//| Rückgabe des Textes                                              |
//+------------------------------------------------------------------+
string CCanvasTable::Text(const int column_index,const int row_index)
  {
   string text="";
//--- Anpassen des Textes, falls nicht gerade die Spaltenbreite geändert wird
   if(m_column_resize_control==WRONG_VALUE)
      text=CorrectingText(column_index,row_index);
//--- Falls die Spaltenbreite nicht gerade geändert wird, dann...
   else
     {
      //--- ...Text anpassen nur der Spalte, deren Breite gerade geändert wird
      if(column_index==m_column_resize_control)
         text=CorrectingText(column_index,row_index);
      //--- Für alle anderen, verwende den vorher angepassten Text
      else
         text=m_vcolumns[column_index].m_text[row_index];
     }
//--- Rückgabe des Textes
   return(text);
      }

Unten ist der Code der Methode CCanvasTable::ChangeColumnWidth(), die die Änderung der Spaltenbreite durchführt.

Die minimale Spaltenbreite beträgt 30 Pixel. Die Methode wird verlassen, wenn der die Spaltenköpfe ausgeblendet werden. Nach bestandener Prüfung wird der Fokus auf die Grenzen der Spaltenköpfe überprüft. Ergibt diese Prüfung, dass der Prozess noch nicht begonnen/beendet wurde, werden die Hilfsvariablen gelöscht und die Methode verlassen. Ist der Prozess im Gang, wird die relative X-Koordinate abgefragt. Wenn der Prozess gerade begonnen hat, dann müssen die aktuelle X-Koordinate des Kursors (die Variable x_fixed) und die Breite der sich verändernden Breite (die Variable prev_width) gesichert werden. Die lokalen Variablen dafür sind definiert als static. Daher wird jedes Mal, wenn die Methode zu Beginn der Methode ihre Werte gesichert, bis sie am Ende des Vorgangs gelöscht werden. 

Jetzt wird die neue Spaltenbreite berechnet. Passiert es, dass die minimale Spaltenbreite erreicht wurde, wird die Methode verlassen. Sonst wird die neue Breite dieser Spalte in der Struktur der Eigenschaften der Tabelle gesichert. Danach werden die Dimensionen der Tabelle erneut berechnet und angewendet, und die Tabelle wird am Ende der Methode neu gezeichnet. 

class CCanvasTable : public CElement
  {
private:
   //--- Minimale Spaltenbreite
   int               m_min_column_width;
   //---
private:
   //--- Ändern der Breite der veränderten Spalte
   void              ChangeColumnWidth(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCanvasTable::CCanvasTable(void) : m_min_column_width(30)
  {
   ...
  }
//+------------------------------------------------------------------+
//| Ändern der Breite der veränderten Spalte                         |
//+------------------------------------------------------------------+
void CCanvasTable::ChangeColumnWidth(void)
  {
//--- Verlassen, wenn die Spaltenköpfe deaktiviert sind
   if(!m_show_headers)
      return;
//--- Prüfen des Fokus auf die Grenzen der Spaltenköpfe
   CheckColumnResizeFocus();
//--- Hilfsvariablen
   static int x_fixed    =0;
   static int prev_width =0;
//--- Wenn fertig, Rücksetzen der Werte
   if(m_column_resize_control==WRONG_VALUE)
     {
      x_fixed    =0;
      prev_width =0;
      return;
     }
//--- Abfrage des Abstandes zur X-Achse
   int xoffset=(int)m_headers.GetInteger(OBJPROP_XOFFSET);
//--- Abfrage der relativen Koordinaten des Mauskursors
   int x=m_mouse.X()-m_headers.X()+xoffset;
//--- Wenn die Breitenänderung einer Spalte gerade begonnen hat
   if(x_fixed<1)
     {
      //--- Sichern der aktuellen X-Koordinate der Spalte
      x_fixed    =x;
      prev_width =m_vcolumns[m_column_resize_control].m_width;
     }
//--- Berechnen der neuen Breite der Spalte
   int new_width=prev_width+(x-x_fixed);
//--- Verlassen, unverändert, wenn das angegebene Limit erreicht wurde
   if(new_width<m_min_column_width)
      return;
//--- Sichern der neuen Spaltenbreite
   m_vcolumns[m_column_resize_control].m_width=new_width;
//--- Berechnen der Tabellengröße
   CalculateTableSize();
//--- Größenanpassung der Tabelle
   ChangeTableSize();
//--- Zeichnen der Tabelle
   DrawTable();
      }

Das Ergebnis sieht wie folgt aus:

 Fig. 5. Anpassung der Textlängen relativ zur variablen Spaltenbreite.

Fig. 5. Anpassung der Textlängen relativ zur variablen Spaltenbreite. 

 


Ereignisbehandlung

Die Farbgebung der Objekte der Tabellen und Änderung der Spaltenbreite wird vom Steuerelement über den Ereignisbehandlung der Maus (CHARTEVENT_MOUSE_MOVE) durchgeführt. 

//+------------------------------------------------------------------+
//| Ereignisbehandlung                                               |
//+------------------------------------------------------------------+
void CCanvasTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handhabung eines Ereignis der Maus
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Verlassen, wenn ausgeblendet
      if(!CElementBase::IsVisible())
         return;
      //--- Verlassen, wenn die Zahl der Unterfenster nicht passt
      if(!CElementBase::CheckSubwindowNumber())
         return;
      //--- Prüfen des Fokus auf in Element
      CElementBase::CheckMouseFocus();
      m_headers.MouseFocus(m_mouse.X()>m_headers.X() && m_mouse.X()<m_headers.X2() &&
                           m_mouse.Y()>m_headers.Y() && m_mouse.Y()<m_headers.Y2());
      //--- Wenn die Bildlaufleiste aktiv ist
      if(m_scrollv.ScrollBarControl() || m_scrollh.ScrollBarControl())
        {
         ShiftTable();
         return;
        }
      //--- Ändern der Farben eines Objektes
      ChangeObjectsColor();
      //--- Ändern der Spaltenbreite
      ChangeColumnWidth();
      return;
     }
   ...
      }

Ein neuer Identifikator für die Ereignisbehandlung wird für den veränderten Status der linken Maustaste benötigt. Damit können wir sich wiederholende Prüfungen übergehen, und das Ereignis gleichzeitig in mehrere Codeblocks der Ereignisbehandlung bearbeiten. Add the ON_CHANGE_MOUSE_LEFT_BUTTON identifier to the Define.mqh file: 

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
  #define ON_CHANGE_MOUSE_LEFT_BUTTON (33) // Geänderter Status der linken  Maustaste

Weiters wurde die Methode CMouse::CheckChangeLeftButtonState() der Klasse hinzugefügt, um damit die aktuellen Parameter der Maus abzufragen (CMouse). Das erlaubt die Bestimmung des geänderten Zustandes der linken Maustaste. Diese Methode wird von der Ereignisbehandlung der Klasse aufgerufen. Hat sich der Zustand der linken Maustaste geändert, sendet diese Methode eine Nachricht mit dem Identifikator ON_CHANGE_MOUSE_LEFT_BUTTON. Diese Nachricht kann später von allen empfangen und bearbeitet werden. 

//+------------------------------------------------------------------+
//| Klasse zur Abfrage der Mausparameter                             |
//+------------------------------------------------------------------+
class CMouse
  {
private:
   //--- Prüfen der Zustandsänderung der linken Maustaste
   bool              CheckChangeLeftButtonState(const string mouse_state);
  };
//+------------------------------------------------------------------+
//| Ereignisbehandlung der bewegten Maus                             |
//+------------------------------------------------------------------+
void CMouse::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handhabung eines Ereignis der Maus
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Koordinaten und Zustand der linken Taste der Maus
      m_x                 =(int)lparam;
      m_y                 =(int)dparam;
      m_left_button_state =CheckChangeLeftButtonState(sparam);
      ...
     }
  }
//+------------------------------------------------------------------+
//| Prüfen der Zustandsänderung der linken Maustaste                 |
//+------------------------------------------------------------------+
bool CMouse::CheckChangeLeftButtonState(const string mouse_state)
  {
   bool left_button_state=(bool)int(mouse_state);
//--- Senden einer Nachricht über die Zustandsänderung der linken Maustaste
   if(m_left_button_state!=left_button_state)
      ::EventChartCustom(m_chart.ChartId(),ON_CHANGE_MOUSE_LEFT_BUTTON,0,0.0,"");
//---
   return(left_button_state);
      }

Die Ereignisbehandlung des Identifikators ON_CHANGE_MOUSE_LEFT_BUTTON wird in der Klasse CCanvasTable benötigt:

  •  um Variablen zu löschen;
  •  die Bildlaufleiste anzupassen;
  •  die Tabelle neu zu zeichnen
//+------------------------------------------------------------------+
//| Ereignisbehandlung                                               |
//+------------------------------------------------------------------+
void CCanvasTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Zustandsänderung der linken Maustaste
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE_MOUSE_LEFT_BUTTON)
     {
      //--- Verlassen, wenn die Spaltenköpfe deaktiviert sind
      if(!m_show_headers)
         return;
      //--- Wenn die linke Maustaste losgelassen wurde
      if(!m_mouse.LeftButtonState())
        {
         //--- Rücksetzen des Modus der Breitenänderung
         m_column_resize_control=WRONG_VALUE;
         //--- Ausblenden des Kursors
         m_column_resize.Hide();
         //--- Anpassen der Bildlaufleiste unter Berücksichtigung der letzten Änderungen
         HorizontalScrolling(m_scrollh.CurrentPos());
        }
      //--- Rücksetzen des Index des letzten Fokus auf einen Spaltenkopf
      m_prev_header_index_focus=WRONG_VALUE;
      //--- Ändern der Farben eines Objektes
      ChangeObjectsColor();
     }
      }

Die animierten Bildschirmfotos des Artikels zeigen das Verhalten einer MQL-Anwendung, die mittels des Links unten für weiteres Experimentieren herunter geladen werden kann.

 

Schlussfolgerung

Die letzte Aktualisierung der Bibliothek verbessert die Tabellendarstellung des Typs CCanvasTable. Dies ist nicht die letzte Version der Tabelle. Sie wird weiterentwickelt und neue Funktionen werden hinzugefügt werden.

Das aktuelle Schema der Bibliothek zur Erstellung eines grafischen Interfaces ist unten aufgeführt.

 Fig. 6. Struktur der Bibliothek im augenblicklichen Entwicklungszustand.

Fig. 6. Struktur der Bibliothek im augenblicklichen Entwicklungszustand.

 

Unten könne Sie die letzten Versionen der Bibliothek und der Dateien zum Testen herunterladen.

Bei Fragen zur Verwendung des bereitgestellten Materials, können Sie auf die detaillierten Beschreibungen im Lauf der Entwicklung der Bibliothek in den vorangegangenen Artikeln dieser Serie zurückgreifen oder Sie stellen Ihre Fragen im Kommentarteil diese Artikels.  

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/3030

Beigefügte Dateien |
Muster, die beim Handeln mit Währungskörben verfügbar sind. Teil II. Muster, die beim Handeln mit Währungskörben verfügbar sind. Teil II.
Das ist der zweite Teil des Artikels über die Muster, die Trader beim Handeln mit Währungspaaren erkennen können. In diesem Teil werden die Muster betrachtet, die bei der Verwendung vereinigter Trendindikatoren festgestellt werden können. Als Analysewerkzeug wurden die Indikatoren verwendet, die auf einem Währungsindex basieren.
Universeller Kanal mit grafischem Interface Universeller Kanal mit grafischem Interface
Alle Channel Indikatoren stellen drei Linien dar: zentrale, obere und untere Linie. Die zentrale Linie ist nach dem Prinzip des Zeichnens dem gleitenden Durchschnitt identisch, und in den meisten Fällen wird für das Zeichnen eines Kanals gerade der gleitende Durchschnitt verwendet. Die obere und untere Linien sind von der zentralen Linie gleich weit entfernt. Dieser Abstand kann in Punkten oder in Prozent vom Preis (Envelopes Indikator) definiert werden, es können der Wert der Standardabweichung (Bolliger Bands) oder der Wert des ATR Indikators (Keltner Kanal) verwendet werden.
Visualisierung! Eine grafische MQL5 Bibliothek ähnlich 'plot' der Sprache R Visualisierung! Eine grafische MQL5 Bibliothek ähnlich 'plot' der Sprache R
Beim Studium der Handelslogik hat die visuelle Darstellung durch Grafiken eine großer Bedeutung. Eine Reihe von Programmiersprachen, die in der wissenschaftlichen Gemeinschaft weit verbreitet sind (wie R und Python), verfügen über eine spezielle "plot"-Funktion für die Visualisierung von Daten. Sie ermöglicht das Zeichnen von Linien, Gruppen von Punkten und Histogramme, um Muster darzustellen. In MQL5 können wir das Gleiche mit der Klasse CGraphics erreichen.
Grafische Interfaces X: mehrzeiliges Textfeld (build 8) Grafische Interfaces X: mehrzeiliges Textfeld (build 8)
Beschreibung eines mehrzeiligen Textfeldes. Anders als bei Grafikobjekten des Typs OBJ_EDIT ist die vorgestellte Version nicht durch eine Anzahl von Buchstaben beschränkt. Es gibt auch die Funktionen eines einfachen Editors, mit einem durch Maus oder Tasten bewegten Kursor.