Grafische Interfaces X: Neue Möglichkeiten der Tabellendarstellung (build 9)
Anatoli Kazharski | 31 März, 2017
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:
- Grafische Interfaces I: Vorbereitung der Bibliotheksstruktur (Kapitel 1)
- Grafische Interfaces X: Updates für die Easy And Fast Bibliothek (Build 3)
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.
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.
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.
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.
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.
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.
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.
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.