
Grafische Interfaces X: Updates für die Tabellendarstellung und ein optimierter Code (build 10)
Inhalt
- Einführung
- Relative Koordinaten des Mauskursors vor einem angegebenem Hintergrund
- Änderungen in der Struktur der Tabellen
- Bestimmen der Zeilenbreite im sichtbaren Teil
- Icons in Tabellenzellen
- Hervorheben der Tabellenzeile unter dem Kursor
- Methoden die Tabellenzellen neu zu zeichnen
- Anwendung zum Testen der Kontrollelemente
- Schlussfolgerung
Einführung
Der erste Artikel Grafische Interfaces I: Vorbereitung der Bibliotheksstruktur (Kapitel 1) beschreibt im Detail den Zweck der Bibliothek. Sie finden eine Liste von Artikeln mit Verweisen am Ende jeden Kapitels. Dort können Sie auch die komplette, aktuelle Version der Bibliothek zum derzeitigen Entwicklungsstand herunterladen. Die Dateien müssen im gleichen Verzeichnis wie das Archiv platziert werden.
Wir fahren fort die Tabellendarstellung mit weiteren Merkmalen zu vervollständigen (CCanvasTable). Folgende Merkmale werden diesmal ergänzt.
- Hervorheben der Tabellenzeile unter dem Kursor.
- Die Möglichkeit jeder Zelle Icons in einem Array zuzuweisen mit einer Methode sie zu wechseln.
- Die Möglichkeit während der Laufzeit Text einer Zelle zu setzen und verändern.
Zusätzlich wurde der Code und bestimmte Algorithmen optimiert, um Tabellen schneller neuzuzeichnen.
Relative Koordinaten des Mauskursors vor einem angegebenem Hintergrund
Um doppelten Code für die Berechnung relativen Koordinaten vor einem Hintergrund in vielen Klassen zu eliminieren, wurden die Methoden CMouse::RelativeX() und CMouse::RelativeY() der Klasse CMouse für den Abruf der Koordinaten hinzugefügt. Es muss diesen Methoden eine Referenz auf ein Objekt mit dem Typ CRectCanvas übergeben werden, um, unter Berücksichtigung des aktuellen Abstandes zum sichtbaren Teil des Hintergrundes die relativen Koordinaten zu berechnen.
//+------------------------------------------------------------------+ //| Klasse zur Abfrage der Mausparameter | //+------------------------------------------------------------------+ class CMouse { public: //--- Rückgabe der relativen Koordinaten des Mauskursors vor dem übergebenen Hintergrundes int RelativeX(CRectCanvas &object); int RelativeY(CRectCanvas &object); }; //+------------------------------------------------------------------+ //| Rückgabe der relativen X-Koordinate des Mauskursor | //| des übergebenen Hintergrundobjektes | //+------------------------------------------------------------------+ int CMouse::RelativeX(CRectCanvas &object) { return(m_x-object.X()+(int)object.GetInteger(OBJPROP_XOFFSET)); } //+------------------------------------------------------------------+ //| Rückgabe der relativen Y-Koordinate des Mauskursor | //| des übergebenen Hintergrundobjektes | //+------------------------------------------------------------------+ int CMouse::RelativeY(CRectCanvas &object) { return(m_y-object.Y()+(int)object.GetInteger(OBJPROP_YOFFSET)); }
In der weiteren Entwicklung der Bibliothek werden diese Methoden dazu verwendet, die relativen Koordinaten aller gezeichnet Kontrollelement abzufragen.
Änderungen in der Struktur der Tabellen
Um die Ausführung des Codes höchst möglichst zu optimieren, war es notwendig den Typ CTOptions der Struktur einer Tabelle etwas zu ändern, zu ergänzen und weitere, neue Strukturen hinzufügen, um so multidimensionale Tabellen zu erstellen. Die Aufgabe hier ist bestimmte Teile der Tabelle auf Basis vorher berechneter Werte neuzuzeichnen. Das könnten zum Beispiel die Koordinaten der Grenzen der Tabellenspalten und -zeilen sein.
So ist zum Beispiel für das Berechnen und Sichern der X-Koordinate der Spaltengrenze nur in der Methode CCanvasTable::DrawGrid() erforderlich, die zum Zeichnen des Gitters der ganzen Tabelle verwendet wird. Und, wenn ein Nutzer eine Tabellenzeile wählt, können die vorgegebenen Werte verwendet werden. Dasselbe gilt für das Hervorheben von Tabellenzeilen, wenn die Maus über einer ist (das wird weiter unten besprochen).
Wir erstellen eine eigene Struktur (CTRowOptions) und deklarieren einen Array seiner Instanzen, um die Y-Koordinaten der Tabellenzeilen zu sichern und möglicher anderer, zukünftiger Eigenschaften einer Zeile. Die Y-Koordinaten werden in der Methode CCanvasTable::DrawRows() berechnet, die den Hintergrund der Zeilen zeichnet. Das diese Methode vor dem Zeichnen des Gitters aufgerufen wird, verwendet die Methode CCanvasTable::DrawGrid() die vorabberechneten Werte aus der Struktur CTRowOptions.
Wir erstellen eigene Struktur des Typs CTCell zum Sichern der Eigenschaften der Zellen. Der Array der Instanzen in der Struktur CTRowOptions wird als dieser Typ deklariert, als ein Array von Tabellenzeilen. Diese Struktur speichert:
- Array der Icons
- Array der Icongrößen
- Index der ausgewählten (angezeigten) Icons einer Zelle
- Volltext
- Textteil
- Schriftfarbe
Da jedes Icon ein Array von Pixeln ist, wird, um sie zu sichern, eine eigene Struktur (CTImage) mit einem dynamischen Array benötigt. Der Code dieser Strukturen findet sich unten:
class CCanvasTable : public CElement { private: //--- Array der Pixel der Icons struct CTImage { uint m_image_data[]; }; //--- Eigenschaften der Tabellenzellen struct CTCell { CTImage m_images[]; // Array der Icons uint m_image_width[]; // Array der Iconbreite uint m_image_height[]; // Array der Iconhöhe int m_selected_image; // Index des gewählten (angezeigten) Icons string m_full_text; // Volltext string m_short_text; // Textteil color m_text_color; // Textfarbe }; //--- Array der Zeilen und der Eigenschaften von Tabellenspalten struct CTOptions { int m_x; // X-Koordinate der linken Rand der Spalte int m_x2; // X-Koordinate der rechten Rand der Spalte int m_width; // Spaltenbreite ENUM_ALIGN_MODE m_text_align; // Art der Textausrichtung in der Zelle der Spalte int m_text_x_offset; // Textabstand string m_header_text; // Text des Spaltenkopfes CTCell m_rows[]; // Array der Tabellenzeilen }; CTOptions m_columns[]; //--- Array der Eigenschaften der Tabellenzeilen struct CTRowOptions { int m_y; // Y-Koordinate der Oberkante der Zeile int m_y2; // Y-Koordinate der Unterkante der Zeile }; CTRowOptions m_rows[]; };
Die entsprechenden Änderungen wurden in allen Methoden vorgenommen, in denen diese Daten verwendet werden.
Bestimmen der Zeilenbreite im sichtbaren Teil
Da eine Tabelle mehrere Zeilen haben kann, würde die Suche nach dem Fokus gefolgt von einem Neuzeichnen der Tabelle die Ausführung signifikant verlangsamen. Dasselbe würde bei der Auswahl von Zeilen und dem Anpassen der Textlängen während einer manuellen Änderung der Spaltenbreite passieren. Um Verzögerungen zu vermeiden, müssen der erste und letzte Index des sichtbaren Teils der Tabelle festgestellt werden, um nur in diesem Bereich mit einer Schleife zu arbeiten. Die Methode CCanvasTable::VisibleTableIndexes() wurde zu diesem Zweck implementiert. Sie bestimmt als erstes die Grenzen des sichtbaren Teils. Die Obergrenze ist der Abstand des sichtbaren Teils entlang der Y-Achse, und die Untergrenze definiert sich aus Obergrenze + Höhe des sichtbaren Teils entlang der Y-Achse.
Jetzt genügt es, die erhaltenen Werte der Grenzen durch Zeilenhöhe aus der Definition der Tabelle zu dividieren, um den Index der obersten und untersten Zeile des sichtbaren Teils zu bestimmen. Falls der Bereich der letzten Zeile überschritten wurde, wird eine Anpassung am Ende der Methode durchgeführt.
class CCanvasTable : public CElement { private: //--- Bestimmen der Indizes des sichtbaren Teils der Tabelle int m_visible_table_from_index; int m_visible_table_to_index; //--- private: //--- Bestimmen der Indizes des sichtbaren Teils der Tabelle void VisibleTableIndexes(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CCanvasTable::CCanvasTable(void) : m_visible_table_from_index(WRONG_VALUE), m_visible_table_to_index(WRONG_VALUE) { ... } //+------------------------------------------------------------------+ //| Bestimmen der Indizes des sichtbaren Teils der Tabelle | //+------------------------------------------------------------------+ void CCanvasTable::VisibleTableIndexes(void) { //--- Bestimmen der Grenzen unter Berücksichtigung des Abstandes des sichtbaren Teils der Tabelle int yoffset1 =(int)m_table.GetInteger(OBJPROP_YOFFSET); int yoffset2 =yoffset1+m_table_visible_y_size; //--- Bestimmen des ersten und letzten Index des sichtbaren Teils der Tabelle m_visible_table_from_index =int(double(yoffset1/m_cell_y_size)); m_visible_table_to_index =int(double(yoffset2/m_cell_y_size)); //--- Erhöhen des niedrigeren Index um eins, wenn nicht außerhalb m_visible_table_to_index=(m_visible_table_to_index+1>m_rows_total)? m_rows_total : m_visible_table_to_index+1; }
Der Index wird in der Methode CCanvasTable::DrawTable() bestimmt. Dieser Methode kann ein Argument übergeben werden, so dass nur der sichtbare Teil der Tabelle neuzuzeichnen ist. Standardmäßig ist dieses Argument false, welches ein Neuzeichnen der ganzen Tabelle nachsichzieht. Der Code unten zeigt verkürzt diese Methode.
//+------------------------------------------------------------------+ //| Tabelle zeichnen | //+------------------------------------------------------------------+ void CCanvasTable::DrawTable(const bool only_visible=false) { //--- Falls nicht nur der sichtbare Teil der Tabelle neu gezeichnet werden soll if(!only_visible) { //--- Setzen der Zeilenindizes der ganzen Tabelle von Anfang bis Ende m_visible_table_from_index =0; m_visible_table_to_index =m_rows_total; } //--- Abfragen des Zeilenindex des sichtbaren Teils der Tabelle else VisibleTableIndexes(); //--- Zeichnen des Hintergrundes der Tabellenzeile //--- Zeichnen der gewählten Zeile //--- Zeichnen des Gitters //--- Zeichnen des Icons //--- Textausgabe //--- Anzeigen der letzten gezeichneten Änderungen //--- Aktualisieren der Spaltenköpfe, wenn sie aktiv sind //--- Anpassen der Tabelle relativ zu den Bildlaufleisten }
Es muss in dieser Methode auch CCanvasTable::VisibleTableIndexes() aufgerufen werden, um den Fokus der Tabellenzeilen zu bestimmen:
//+------------------------------------------------------------------+ //| Prüfen des Fokus auf die Tabellenzeilen | //+------------------------------------------------------------------+ int CCanvasTable::CheckRowFocus(void) { int item_index_focus=WRONG_VALUE; //--- Abfrage der relativen Y-Koordinate unter dem Mauskursor int y=m_mouse.RelativeY(m_table); ///--- Abfrage des Index des lokalen Bereiches der Tabelle VisibleTableIndexes(); //--- Suchen des Fokus for(int i=m_visible_table_from_index; i<m_visible_table_to_index; i++) { //--- Wenn sich der Fokus auf Zeile ändert if(y>m_rows[i].m_y && y<=m_rows[i].m_y2) { item_index_focus=i; break; } } //--- Rückgabe des Index der Zeile im Fokus return(item_index_focus); }
Icons in Tabellenzellen
Es können jeder Zelle mehrere Icons zugewiesen werden, die während der Laufzeit gewechselt werden können. Die neuen Variablen und Methoden für den Abstand der Icons vom oberen und linken Rand der Zelle:
class CCanvasTable : public CElement { private: //--- Iconabstand vom Zellenrand int m_image_x_offset; int m_image_y_offset; //--- public: //--- Iconabstand vom Zellenrand void ImageXOffset(const int x_offset) { m_image_x_offset=x_offset; } void ImageYOffset(const int y_offset) { m_image_y_offset=y_offset; } };
Um ein Icon einer angegebenen Zelle zuzuweisen, muss ein Array mit seinem Pfad des lokalen Verzeichnisses des Terminals übergeben werden. Davor müssen sie von der MQL-Anwendung als Ressource (#resource) geladen werden. Die Methode CCanvasTable::SetImages() erfüllt diesen Zweck. Wird eine leerer Array übergeben oder ein Array-Überlauf registriert, wird die Methode verlassen.
Nach bestandenen Prüfungen werden die Arrays der Zellen neu dimensioniert. Danach werden in einer Schleife mit der Methode ::ResourceReadImage() die Icons in einen eindimensionalen Array eingelesen mit der Farbe für jeden Pixel. Die Icongrößen werden im entsprechenden Array gesichert. Die werden für die Dimension der Schleife zum Zeichnen der Icons auf dem Hintergrund benötigt. Standardmäßig ist immer das erste Icon einer Zelle ausgewählt.
class CCanvasTable : public CElement { public: //--- Bestimmen der Icons für die angegebenen Zelle void SetImages(const uint column_index,const uint row_index,const string &bmp_file_path[]); }; //+------------------------------------------------------------------+ //| Bestimmen der Icons für die angegebenen Zelle | //+------------------------------------------------------------------+ void CCanvasTable::SetImages(const uint column_index,const uint row_index,const string &bmp_file_path[]) { int total=0; //--- Verlassen, wenn der Array leer ist if((total=CheckArraySize(bmp_file_path))==WRONG_VALUE) return; //--- Prüfen der Arraygrenze if(!CheckOutOfRange(column_index,row_index)) return; //--- Größenänderung des Arrays ::ArrayResize(m_columns[column_index].m_rows[row_index].m_images,total); ::ArrayResize(m_columns[column_index].m_rows[row_index].m_image_width,total); ::ArrayResize(m_columns[column_index].m_rows[row_index].m_image_height,total); //--- for(int i=0; i<total; i++) { //--- Standardauswahl des ersten Icons m_columns[column_index].m_rows[row_index].m_selected_image=0; //--- Schreiben des übergebenen Icons in den Array und sichern dessen Größe if(!ResourceReadImage(bmp_file_path[i],m_columns[column_index].m_rows[row_index].m_images[i].m_image_data, m_columns[column_index].m_rows[row_index].m_image_width[i], m_columns[column_index].m_rows[row_index].m_image_height[i])) { Print(__FUNCTION__," > error: ",GetLastError()); return; } } }
Um herauszufinden, wie viele Icons eine bestimmte Zelle hat, verwenden wir die Methode CCanvasTable::ImagesTotal():
class CCanvasTable : public CElement { public: //--- Rückgabe der Gesamtzahl der Icons der angegebenen Zelle int ImagesTotal(const uint column_index,const uint row_index); }; //+------------------------------------------------------------------+ //| Rückgabe der Gesamtzahl der Icons der angegebenen Zelle | //+------------------------------------------------------------------+ int CCanvasTable::ImagesTotal(const uint column_index,const uint row_index) { //--- Prüfen der Arraygrenze if(!CheckOutOfRange(column_index,row_index)) return(WRONG_VALUE); //--- Rückgabe der Arraygröße der Icons return(::ArraySize(m_columns[column_index].m_rows[row_index].m_images)); }
Betrachten wir jetzt die Methoden zum Zeichnen der Icons. Zuerst wurde eine neue Methode CColors::BlendColors() der Klasse CColors ergänzt, die die obere und untere Farbe unter Berücksichtigung der Transparenz des darüber gelegten Icons mischt. So wie die Hilfsmethode CColors::GetA(), die den Transparenzwert der übergebenen Farbe ermittelt.
In der Methode CColors::BlendColors() wird die übergebene Farbe in ihre RGB-Komponenten aufgeteilt, und der Alphakanal wird aus der obersten Farbe extrahiert. Der Alphakanal wird in einen Wert zwischen Null und Eins konvertiert. Ist die übergebene Farbe intransparent, wird die Farbvermischung nicht durchgeführt. Im Falle von Transparenz wird jede Komponente der beiden Farben unter Berücksichtigung der oberen Farbe gemischt. Danach werden die Werte der erhaltenen Komponenten angepasst, sollten sie den Wert von 255 übersteigen.
//+------------------------------------------------------------------+ //| Klasse für das Arbeiten mit Farben | //+------------------------------------------------------------------+ class CColors { public: double GetA(const color aColor); color BlendColors(const uint lower_color,const uint upper_color); }; //+------------------------------------------------------------------+ //| Ermitteln der A-Komponente des Wertes | //+------------------------------------------------------------------+ double CColors::GetA(const color aColor) { return(double(uchar((aColor)>>24))); } //+------------------------------------------------------------------+ //| Mischen zweier Farben gemäß der Transparenz der oberen Farbe | //+------------------------------------------------------------------+ color CColors::BlendColors(const uint lower_color,const uint upper_color) { double r1=0,g1=0,b1=0; double r2=0,g2=0,b2=0,alpha=0; double r3=0,g3=0,b3=0; //--- Konvertieren der Farbe in das Format ARGB uint pixel_color=::ColorToARGB(upper_color); //--- Ermitteln der Komponenten der unteren und oberen Farbe ColorToRGB(lower_color,r1,g1,b1); ColorToRGB(pixel_color,r2,g2,b2); //--- Ermitteln der Prozentsatzes der Transparenz zw. 0.00 und 1.00 alpha=GetA(upper_color)/255.0; //--- Gibt es Transparenz if(alpha<1.0) { //--- Mischen beider Farben gemäß des Alphakanals r3=(r1*(1-alpha))+(r2*alpha); g3=(g1*(1-alpha))+(g2*alpha); b3=(b1*(1-alpha))+(b2*alpha); //--- Anpassen der erhaltenen Werte r3=(r3>255)? 255 : r3; g3=(g3>255)? 255 : g3; b3=(b3>255)? 255 : b3; } else { r3=r2; g3=g2; b3=b2; } //--- Kombinieren der Komponenten und Rückgabe der Farbe return(RGBToColor(r3,g3,b3)); }
Damit wird es leicht eine Methode zu schreibe, die die Icons zeichnet. Der Code der Methode CCanvasTable::DrawImage() ist unten angeführt. Ihr muss der Index der Zelle übergeben werden, auf der das Icon gezeichnet werden soll. Zu Beginn der Methode werden die Koordinaten des Icons unter Berücksichtigung der Abstände ermittelt, so wie der Index der gewählten Zelle und ihre Größe. Dann liefert eine Doppelschleife das Icon, Pixel für Pixel. Ist ein Pixel leer, d.h. ohne angegebene Farbe, geht die Schleife zum nächsten Pixel. Gibt es eine Farbe, werden die Farbe des Zellhintergrunds und die aktuelle Pixelfarbe bestimmt und beide Farben werden unter Berücksichtigung der Transparenz der oberen Farbe vermischt und die sich ergebende Farbe wird auf dem Hintergrund gezeichnet.
class CCanvasTable : public CElement { private: //--- Zeichnen eines Icons in der angegebenen Zelle void DrawImage(const int column_index,const int row_index); }; //+------------------------------------------------------------------+ //| Zeichnen eines Icons in der angegebenen Zelle | //+------------------------------------------------------------------+ void CCanvasTable::DrawImage(const int column_index,const int row_index) { //--- Berechnen der Koordinaten int x =m_columns[column_index].m_x+m_image_x_offset; int y =m_rows[row_index].m_y+m_image_y_offset; //--- Ausgewähltes Icon der Zelle mit seiner Größe int selected_image =m_columns[column_index].m_rows[row_index].m_selected_image; uint image_height =m_columns[column_index].m_rows[row_index].m_image_height[selected_image]; uint image_width =m_columns[column_index].m_rows[row_index].m_image_width[selected_image]; //--- Zeichnen for(uint ly=0,i=0; ly<image_height; ly++) { for(uint lx=0; lx<image_width; lx++,i++) { //--- Gibt es keine Farbe, gehe zum nächsten Pixel if(m_columns[column_index].m_rows[row_index].m_images[selected_image].m_image_data[i]<1) continue; //--- Ermitteln der Farbe der unteren Schicht (Zellhintergrund) und der Farbe des jew. Pixels des Icons uint background =(row_index==m_selected_item)? m_selected_row_color : m_table.PixelGet(x+lx,y+ly); uint pixel_color =m_columns[column_index].m_rows[row_index].m_images[selected_image].m_image_data[i]; //--- Mischen der Farben uint foreground=::ColorToARGB(m_clr.BlendColors(background,pixel_color)); //--- Zeichnen der Pixel des darüber gelegten Icons m_table.PixelSet(x+lx,y+ly,foreground); } } }
Die Methode CCanvasTable::DrawImages() zeichnet alle Icons auf einmal in die Tabelle, abhängig davon, ob nur der sichtbare Teil der Tabelle gezeichnet werden muss. In der aktuellen Version der Tabelle können die Icons nur dann gezeichnet werden, wenn die Textausrichtung linksbündig ist. Zusätzlich wird in jeder Iteration geprüft, ob ein Icon der Zelle zugewiesen ist und ob dessen Array der Pixel leer ist. Nach allen Prüfungen wird die Methode CCanvasTable::DrawImage() zum Zeichnen der Icons aufgerufen.
class CCanvasTable : public CElement { private: //--- Zeichnen aller Icons der Tabelle void DrawImages(void); }; //+------------------------------------------------------------------+ //| Zeichnen aller Icons der Tabelle | //+------------------------------------------------------------------+ void CCanvasTable::DrawImages(void) { //--- Berechnen der Koordinaten int x=0,y=0; //--- Spalten for(int c=0; c<m_columns_total; c++) { //--- Wenn die Textausrichtung nicht linksbündig ist, gehe zur nächsten Spalte if(m_columns[c].m_text_align!=ALIGN_LEFT) continue; //--- Zeilen for(int r=m_visible_table_from_index; r<m_visible_table_to_index; r++) { //--- Nächste Zelle, wenn dieser kein Icon zugewiesen wurde if(ImagesTotal(c,r)<1) continue; //--- Das gewählte Icon der Zelle (standardmäßig das erste [0]) int selected_image=m_columns[c].m_rows[r].m_selected_image; //--- Nächste Zelle, wenn das Array der Pixel leer ist if(::ArraySize(m_columns[c].m_rows[r].m_images[selected_image].m_image_data)<1) continue; //--- Zeichnen des Icons DrawImage(c,r); } } }
Der Screenshot unten zeigt das Beispiel einer Tabelle mit Icons in den Zellen:
Fig. 1. Tabelle mit Icons in den Zellen.
Hervorheben der Tabellenzeile unter dem Kursor
Um eine Zeile der gezeigten Tabelle hervorzuheben, wenn sich die Maus über ihr befindet, benötigen wir weitere Variablen und Methoden. Um das Hervorheben zu aktivieren, verwenden wir die Methode CCanvasTable::LightsHover(). Die Zeilenfarbe wird mit Hilfe der Methode CCanvasTable::CellColorHover() festgelegt.
class CCanvasTable : public CElement { private: //--- Zellfarben in verschiedenen Modi color m_cell_color; color m_cell_color_hover; //--- Modus des Hervorhebens der Zeile unter der Maus bool m_lights_hover; //--- public: //--- Zellfarben in verschiedenen Modi void CellColor(const color clr) { m_cell_color=clr; } void CellColorHover(const color clr) { m_cell_color_hover=clr; } //--- Modus des Hervorhebens der Zeile unter der Maus void LightsHover(const bool flag) { m_lights_hover=flag; } };
Das Hervorheben einer Zeile muss nicht ein wiederholtes Neuzeichnen der ganzen Tabelle zur Folge haben, sobald sich die Maus etwas bewegt. Im Gegenteil, das würde massiv die CPU belasten, die Ausführung der Anwendung stark verzögern und ist daher nicht zu empfehlen. Erreicht die Maus das erste Mal oder erneut einen Bereich der Tabelle, genügt es, sich einmal auf den Fokus zu konzentrieren (Iteration über den ganzen Array der Zeilen). Dafür gibt es die Methode CCanvasTable::CheckRowFocus(). Ist der Fokus und der Index dieser Zeile erst gefunden, muss nur noch geprüft werden, ob bei einer Mausbewegung sich die Zeile im Fokus geändert hat. Der beschrieben Algorithmus wurde in der Methode CCanvasTable::ChangeRowsColor() umgesetzt, wie man unten sieht. Die Methode CCanvasTable::RedrawRow() verändert die Zeilenfarbe, ihr Code wird später aufgeführt. Die Methode CCanvasTable::ChangeRowsColor() wird in der Methode CCanvasTable::ChangeObjectsColor() aufgerufen, um die Farbe des Tabellenobjekts zu ändern.
class CCanvasTable : public CElement { private: //--- Bestimmen des Zeilenfokus int m_item_index_focus; //--- Bestimmen des Momentes, da der Mauskursor von einer Zeile zur nächsten wechselt int m_prev_item_index_focus; //--- private: //--- Wechseln der Farbe des Hervorhebens void ChangeRowsColor(void); }; //+------------------------------------------------------------------+ //| Wechseln der Farbe des Hervorhebens | //+------------------------------------------------------------------+ void CCanvasTable::ChangeRowsColor(void) { //--- Verlassen, wenn das Hervorheben deaktiviert ist if(!m_lights_hover) return; //--- Falls nicht im Fokus if(!m_table.MouseFocus()) { //--- Falls noch nicht bekannt, dass es noch nicht im Fokus ist if(m_prev_item_index_focus!=WRONG_VALUE) { m_item_index_focus=WRONG_VALUE; //--- Ändern der Farbe RedrawRow(); m_table.Update(); //--- Rücksetzen des Fokus m_prev_item_index_focus=WRONG_VALUE; } } //--- Wenn im Fokus else { //--- Prüfen des Fokus auf die Tabellenzeilen if(m_item_index_focus==WRONG_VALUE) { //--- Ermitteln des Index der Zeile im Fokus m_item_index_focus=CheckRowFocus(); //--- Ändern der Zeilenfarbe RedrawRow(); m_table.Update(); //--- Sichern als vorheriger Index des Fokus m_prev_item_index_focus=m_item_index_focus; return; } //--- Abfrage der relativen Y-Koordinate unter dem Mauskursor int y=m_mouse.RelativeY(m_table); //--- Bestätigen des Fokus bool condition=(y>m_rows[m_item_index_focus].m_y && y<=m_rows[m_item_index_focus].m_y2); //--- Bei geändertem Fokus if(!condition) { //--- Ermitteln des Index der Zeile im Fokus m_item_index_focus=CheckRowFocus(); //--- Ändern der Zeilenfarbe RedrawRow(); m_table.Update(); //--- Sichern als vorheriger Index des Fokus m_prev_item_index_focus=m_item_index_focus; } } }
Die Methode CCanvasTable::RedrawRow() gewährleistet ein schnelles Neuzeichnen in zwei Modi:
- bei der Auswahl einer Zeile
- im Modus Hervorheben der Zeile unter der Maus.
Die Methode verlangt das entsprechende Argument, um den gewünschten Modus zu bestimmen. Standardmäßig steht dieses Argument auf false, und das steht für den Modus für das Hervorheben von Zeilen unter der Maus. Die Klasse beinhaltet spezielle Variablen für beide Modi, um die aktuellen und die vorher ausgewählte/hervorgehobene Tabellenzeile zu bestimmen. Daher verlangt das Markieren einer neuen Zeile nur das Neuzeichnen der vorherigen und der aktuellen Zeile und nicht ganzen Tabelle.
Die Methode wird verlassen, wenn die Indices nicht definiert sind (WRONG_VALUE). Danach muss ermittelt werden, wie viel Indices definiert wurden. Bewegt sich die Maus das erste Mal in die Tabelle und gibt es nur einen Index (den aktuellen), wird nur die Farbe der aktuellen Zeile geändert. Wird die Maus erneut in die Tabelle bewegt, werden zwei Zeilen geändert (die neue und die letzte).
Jetzt muss die Reihenfolge der Farbänderungen bestimmt werden. Ist der Index der aktuellen Zeile größer als der der vorherigen, hat sich der Kursor nach unten bewegt. Dann wird zuerst die Farbe der vorherigen Zeile geändert und dann die aktuelle. Im umgekehrten Fall, kehren wir das Vorgehen um. Die Methode berücksichtigt auch den Moment des Verlassens der Tabelle, wenn keine aktuelle Zeile bestimmt wurde, es aber noch einen Index für eine vorherige Zeile gibt.
Sind erst einmal alle operativen Variablen initiiert, dann werden der Zeilenhintergrund, das Gitter und die Texte in strenger Reihenfolge gezeichnet.
class CCanvasTable : public CElement { private: //--- Neuzeichnen der angegebenen Tabellenzeile gemäß des angegebenen Modus void RedrawRow(const bool is_selected_row=false); }; //+------------------------------------------------------------------+ //| Neuzeichnen der best. Tabellenzeile gemäß des angegebenen Modus | //+------------------------------------------------------------------+ void CCanvasTable::RedrawRow(const bool is_selected_row=false) { //--- Der aktuelle und der vorherige Zeilenindex int item_index =WRONG_VALUE; int prev_item_index =WRONG_VALUE; //--- Initialisierung der Zeilenindizes gemäß des angegebenen Modus if(is_selected_row) { item_index =m_selected_item; prev_item_index =m_prev_selected_item; } else { item_index =m_item_index_focus; prev_item_index =m_prev_item_index_focus; } //--- Verlassen, wenn die Indizes nicht definiert sind if(prev_item_index==WRONG_VALUE && item_index==WRONG_VALUE) return; //--- Anzahl der zu zeichnenden Zeilen und Spalten int rows_total =(item_index!=WRONG_VALUE && prev_item_index!=WRONG_VALUE)? 2 : 1; int columns_total =m_columns_total-1; //--- Koordinaten int x1=1,x2=m_table_x_size; int y1[2]={0},y2[2]={0}; //--- Array der Werte in einer besonderen Reihenfolge int indexes[2]; //--- Wenn (1) der Mauskursor sich abwärts oder (2) sich das erste Mal in die Tabelle bewegte if(item_index>m_prev_item_index_focus || item_index==WRONG_VALUE) { indexes[0]=(item_index==WRONG_VALUE || prev_item_index!=WRONG_VALUE)? prev_item_index : item_index; indexes[1]=item_index; } //--- Wenn der Kursor sich aufwärts bewegte else { indexes[0]=item_index; indexes[1]=prev_item_index; } //--- Zeichnen des Zeilenhintergrundes for(int r=0; r<rows_total; r++) { //--- Berechnen der Koordinaten der Ober- und Untergrenzen der Zeile y1[r]=m_rows[indexes[r]].m_y+1; y2[r]=m_rows[indexes[r]].m_y2-1; //--- Bestimmen der Zeile im Fokus unter Berücksichtigung des Modus des Hervorhebens bool is_item_focus=false; if(!m_lights_hover) is_item_focus=(indexes[r]==item_index && item_index!=WRONG_VALUE); else is_item_focus=(item_index==WRONG_VALUE)?(indexes[r]==prev_item_index) :(indexes[r]==item_index); //--- Zeichnen des Zeilenhintergrundes m_table.FillRectangle(x1,y1[r],x2,y2[r],RowColorCurrent(indexes[r],is_item_focus)); } //--- Gitterfarbe uint clr=::ColorToARGB(m_grid_color); //--- Zeichnen der Grenzen for(int r=0; r<rows_total; r++) { for(int c=0; c<columns_total; c++) m_table.Line(m_columns[c].m_x2,y1[r],m_columns[c].m_x2,y2[r],clr); } //--- Zeichnen der Icons for(int r=0; r<rows_total; r++) { for(int c=0; c<m_columns_total; c++) { //--- Zeichnen des Icons, wenn (1) es in dieser Zelle existiert und (2) die Textausrichtung dieser Spalte linksbündig ist if(ImagesTotal(c,r)>0 && m_columns[c].m_text_align==ALIGN_LEFT) DrawImage(c,indexes[r]); } } //--- Berechnen der Koordinaten int x=0,y=0; //--- Modus der Textausrichtung uint text_align=0; //--- Zeichnen des Textes for(int c=0; c<m_columns_total; c++) { //--- Ermitteln (1) der X-Koordinate des Textes und (2) die Textausrichtung x =TextX(c); text_align =TextAlign(c,TA_TOP); //--- for(int r=0; r<rows_total; r++) { //--- (1) Berechnen der Koordinaten und (2) Zeichnen des Textes y=m_rows[indexes[r]].m_y+m_text_y_offset; m_table.TextOut(x,y,m_columns[c].m_rows[indexes[r]].m_short_text,TextColor(c,indexes[r]),text_align); } } }
Und das ist das Ergebnis:
Fig. 2. Demonstration des Hervorhebens der Tabellenzeile unter der Maus.
Methoden die Tabellenzellen neu zu zeichnen
Die Methode für das schnelle Neuzeichnen der Tabellenzeilen wurde bereits angesprochen. Die Methode für das schnelle Neuzeichnen einer Zelle wird jetzt gezeigt. Wenn es zum Beispiel notwendig ist, den Text, seine Farbe oder das Icon einer Zelle der Tabelle zu ändern, reicht es nur diese Zelle neu zu zeichnen statt der ganzen Tabelle. Für diesen Zweck verwenden wir die 'private' Methode CCanvasTable::RedrawCell(). Es wird nur der Zellinhalt neu gezeichnet, alles andere nicht berührt. Die Hintergrundfarbe wird unter Berücksichtigung des Modus des Hervorhebens bestimmt, wenn aktiviert. Nach dem Bestimmen der Werte und der Initialisierung der lokalen Variablen, werden der Hintergrund, das Icon (falls zugewiesen und linksbündigem Text) und der Text in der Zelle neu gezeichnet.
class CCanvasTable : public CElement { private: //--- Neuzeichnen der angegebenen Zelle der Tabelle void RedrawCell(const int column_index,const int row_index); }; //+------------------------------------------------------------------+ //| Neuzeichnen der angegebenen Zelle der Tabelle | //+------------------------------------------------------------------+ void CCanvasTable::RedrawCell(const int column_index,const int row_index) { //--- Koordinaten int x1=m_columns[column_index].m_x+1; int x2=m_columns[column_index].m_x2-1; int y1=m_rows[row_index].m_y+1; int y2=m_rows[row_index].m_y2-1; //--- Berechnen der Koordinaten int x=0,y=0; //--- Prüfen des Fokus bool is_row_focus=false; //--- Hervorheben-Modus ist aktiviert if(m_lights_hover) { //--- (1) Ermitteln der relativen Y-Koordinate des Mauskursors und (2) des Fokus der angegebenen Tabellenzeile y=m_mouse.RelativeY(m_table); is_row_focus=(y>m_rows[row_index].m_y && y<=m_rows[row_index].m_y2); } //--- Zeichnen des Zellhintergrunds m_table.FillRectangle(x1,y1,x2,y2,RowColorCurrent(row_index,is_row_focus)); //--- Zeichnen des Icons, wenn (1) es in dieser Zelle existiert und (2) die Textausrichtung dieser Spalte linksbündig ist if(ImagesTotal(column_index,row_index)>0 && m_columns[column_index].m_text_align==ALIGN_LEFT) DrawImage(column_index,row_index); //--- Abfrage der Textausrichtung uint text_align=TextAlign(column_index,TA_TOP); //--- Zeichnen des Textes for(int c=0; c<m_columns_total; c++) { //--- Abfragen der X-Koordinate des Textes x=TextX(c); //--- Schleifenende if(c==column_index) break; } //--- (1) Berechnen der Y-Koordinate, und (2) Zeichnen des Texte y=y1+m_text_y_offset-1; m_table.TextOut(x,y,m_columns[column_index].m_rows[row_index].m_short_text,TextColor(column_index,row_index),text_align); }
Kommen wir nun zu den Methoden, um in der Zelle den Text, die Textfarbe und das Icon (ausgewählt aus den Zugewiesenen) zu ändern. Die 'public' Methoden CCanvasTable::SetValue() und CCanvasTable::TextColor() müssen für den Text und seine Farbe verwendet werden. Diesen Methoden werden die Indizes der Zelle (Spalte und Zeile) und der einzutragene Wert übergeben. Der Methode CCanvasTable::SetValue() muss eine Zeichenkette übergeben werden, der von der Zelle angezeigt wird. Sie sichert sowohl den ganzen Text wie auch die verkürzte Version (wenn der ganze Text zu lang ist für die Zellbreite) in den entsprechenden Variablen der Struktur der Tabelle (CTCell). Die Textfarbe muss der Methode CCanvasTable::TextColor() übergeben werden. Als vierten Parameter kann für beide Methoden angegeben werden, ob es notwendig ist, die Zelle sofort oder erst später neuzuzeichnen ist, und zwar durch den Aufruf der Methode CCanvasTable::UpdateTable().
class CCanvasTable : public CElement { private: //--- Eintragen des Wertes in die angegebene Zelle void SetValue(const uint column_index,const uint row_index,const string value,const bool redraw=false); //--- Bestimmen der Farbe der angegebenen Zelle void TextColor(const uint column_index,const uint row_index,const color clr,const bool redraw=false); }; //+------------------------------------------------------------------+ //| Ausfüllen des Arrays am den angegebenen Indizes | //+------------------------------------------------------------------+ void CCanvasTable::SetValue(const uint column_index,const uint row_index,const string value,const bool redraw=false) { //--- Prüfen der Arraygrenze if(!CheckOutOfRange(column_index,row_index)) return; //--- Sichern der Werte im Array m_columns[column_index].m_rows[row_index].m_full_text=value; //--- Anpassen und Sichern des Textes, wenn er nicht in die Zelle passt m_columns[column_index].m_rows[row_index].m_short_text=CorrectingText(column_index,row_index); //--- Neuzeichnen der Zelle, wenn angegeben if(redraw) RedrawCell(column_index,row_index); } //+------------------------------------------------------------------+ //| Ausfüllen des Farbarrays des Textes | //+------------------------------------------------------------------+ void CCanvasTable::TextColor(const uint column_index,const uint row_index,const color clr,const bool redraw=false) { //--- Prüfen der Arraygrenze if(!CheckOutOfRange(column_index,row_index)) return; //--- Sichern der Textfarbe im allgemeinen Array m_columns[column_index].m_rows[row_index].m_text_color=clr; //--- Neuzeichnen der Zelle, wenn angegeben if(redraw) RedrawCell(column_index,row_index); }
Das Icon der Zelle wird mit der Methode CCanvasTable::ChangeImage() geändert. Der Index der zu ändernden Icons muss als dritter Parameter hier übergeben werden. So wie in den oben beschriebenen Methoden zum Ändern der Zelleigenschaften ist es möglich anzugeben, ob die Zelle sofort oder später neuzuzeichnen sind.
class CCanvasTable : public CElement { private: //--- Ändern des Icons der angegebenen Zelle void ChangeImage(const uint column_index,const uint row_index,const uint image_index,const bool redraw=false); }; //+------------------------------------------------------------------+ //| Ändern des Icons der angegebenen Zelle | //+------------------------------------------------------------------+ void CCanvasTable::ChangeImage(const uint column_index,const uint row_index,const uint image_index,const bool redraw=false) { //--- Prüfen der Arraygrenze if(!CheckOutOfRange(column_index,row_index)) return; //--- Ermitteln der Anzahl der Icons der Zelle int images_total=ImagesTotal(column_index,row_index); //--- Verlassen, wenn (1) es keine Icons gibt oder (2) außerhalb des erlaubten Bereiches if(images_total==WRONG_VALUE || image_index>=(uint)images_total) return; //--- Verlassen, wenn das angegebene Icon dem gewählten entspricht if(image_index==m_columns[column_index].m_rows[row_index].m_selected_image) return; //--- Sichern des Index des gewählten Icons der Zelle m_columns[column_index].m_rows[row_index].m_selected_image=(int)image_index; //--- Neuzeichnen der Zelle, wenn angegeben if(redraw) RedrawCell(column_index,row_index); }
Eine weitere Methode wird für das Neuzeichnen der ganzen Tabelle benötigt — CCanvasTable::UpdateTable(). Sie kann auf zweifache Weise aufgerufen werden:
- Wenn die Tabelle einfach nur aktualisiert werden muss, um die letzten Änderungen durch die oben beschriebenen Methoden anzuzeigen.
- Wenn die Tabelle komplett neu gezeichnet werden soll, nach vorgenommenen Änderungen.
Standardmäßig steht das einzige Argument der Methode auf false, welches eine Aktualisierung ohne Neuzeichnung bedeutet.
class CCanvasTable : public CElement { private: //--- Aktualisieren der Tabelle void UpdateTable(const bool redraw=false); }; //+------------------------------------------------------------------+ //| Aktualisieren der Tabelle | //+------------------------------------------------------------------+ void CCanvasTable::UpdateTable(const bool redraw=false) { //--- Neuzeichnen der Tabelle, wenn angegeben if(redraw) DrawTable(); //--- Aktualisieren der Tabelle m_table.Update(); }
Unten sieht man das Ergebnis der bisherigen Arbeit:
Fig. 3. Demonstration der neuen Eigenschaften der Tabellendarstellung.
Ein Expert Advisor, der die besprochenen Eigenschaften demonstriert, ist in der Datei am Ende des Artikels und kann heruntergeladen werden. Solange dieses Programm läuft, werden die Icons in allen Zellen der Tabelle (5 Spalten und 30 Zeilen) alle 100 Millisekunden gewechselt. Der Screenshot unten zeigt die CPU-Belastung, ohne dass eine Nutzer etwas über das grafischen Interface der MQL-Anwendung eingreift. Die CPU-Last übersteigt bei einer Frequenz von 100 Millisekunden niemals den Wert von 3%.
Fig. 4. Die CPU-Last während der Ausführung der MQL-Testanwendung.
Anwendung zum Testen der Kontrollelemente
Die aktuelle Version der Tabellendarstellung ist bereits "klug" genug, um Tabellen ähnlich wie zum Beispiel dem Datenfenster (Market Watch) nachzubauen. Versuchen wir das einmal. Wir erstellen zum Beispiel eine Tabelle mit 5 Spalten und 25 Zeilen. Es sind die 25 Symbole des Demo-Servers von MetaQuotes. Folgende Daten erscheinen in der Tabelle:
- Symbol – Wertpapiere (Währungspaare).
- Bid – Bid Preis.
- Ask – Ask Preis.
- Spread (!) – Differenz zwischen den Preisen Ask und Bid.
- Time – Zeitpunkt des letzten Datensatzes.
Bereiten wir dieselben Icons vor, die im Datenfenster uns die Richtung der letzten Preisänderungen anzeigen. Die erste Initialisierung der Tabellenzellen geschieht gleich in der Methode, die das Kontrollelement erstellt, und wird durch den Aufruf der Hilfsmethode CProgram :: InitializingTable() der Nutzerklasse ausgeführt.
//+------------------------------------------------------------------+ //| Klasse für die Erstellung der Anwendung | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Initialisierung der Tabelle void InitializingTable(void); }; //+------------------------------------------------------------------+ //| Initialisierung der Tabelle | //+------------------------------------------------------------------+ void CProgram::InitializingTable(void) { //--- Array der Spaltenköpfe string text_headers[COLUMNS1_TOTAL]={"Symbol","Bid","Ask","!","Time"}; //--- Array der Symbole string text_array[25]= { "AUDUSD","GBPUSD","EURUSD","USDCAD","USDCHF","USDJPY","NZDUSD","USDSEK","USDHKD","USDMXN", "USDZAR","USDTRY","GBPAUD","AUDCAD","CADCHF","EURAUD","GBPCHF","GBPJPY","NZDJPY","AUDJPY", "EURJPY","EURCHF","EURGBP","AUDCHF","CHFJPY" }; //--- Array der Icons string image_array[3]= { "::Images\\EasyAndFastGUI\\Icons\\bmp16\\circle_gray.bmp", "::Images\\EasyAndFastGUI\\Icons\\bmp16\\arrow_up.bmp", "::Images\\EasyAndFastGUI\\Icons\\bmp16\\arrow_down.bmp" }; //--- for(int c=0; c<COLUMNS1_TOTAL; c++) { //--- Setzen der Spaltenköpfe m_canvas_table.SetHeaderText(c,text_headers[c]); //--- for(int r=0; r<ROWS1_TOTAL; r++) { //--- Setzen der Icons m_canvas_table.SetImages(c,r,image_array); //--- Setzen der Symbolnamen if(c<1) m_canvas_table.SetValue(c,r,text_array[r]); //--- Standardwerte aller Zellen else m_canvas_table.SetValue(c,r,"-"); } } }
Die Werte der Tabellenzellen werden während der Laufzeit durch den Timer alle 16 Millisekunden aktualisiert. Ein andere Hilfsmethode CProgram::UpdateTable() übernimmt diese Aufgabe. Verlassen wird die Methode am Wochenende (Samstag und Sonntag). Dann iteriert eine Doppelschleife über alle Spalten und Zeilen der Tabelle. Diese Doppelschleife erfragt die letzten beiden Ticks von jedem Symbol und nach der Analyse der Preisänderung, werden die entsprechenden Werte geschrieben.
class CProgram : public CWndEvents { private: //--- Initialisierung der Tabelle void InitializingTable(void); }; //+------------------------------------------------------------------+ //| Aktualisieren der Tabellenwerte | //+------------------------------------------------------------------+ void CProgram::UpdateTable(void) { MqlDateTime check_time; ::TimeToStruct(::TimeTradeServer(),check_time); //--- Verlassen, wenn Samstag oder Sonntag if(check_time.day_of_week==0 || check_time.day_of_week==6) return; //--- for(int c=0; c<m_canvas_table.ColumnsTotal(); c++) { for(int r=0; r<m_canvas_table.RowsTotal(); r++) { //--- Das Symbol der abzufragenden Daten string symbol=m_canvas_table.GetValue(0,r); //--- Abruf der letzten beiden Ticks MqlTick ticks[]; if(::CopyTicks(symbol,ticks,COPY_TICKS_ALL,0,2)<2) continue; //--- Deklarieren des Arrays als Zeitreihe ::ArraySetAsSeries(ticks,true); //--- Symbolspalte - Symbol. Bestimmen der Richtung der Preise. if(c==0) { int index=0; //--- Wenn keine Preisänderung if(ticks[0].ask==ticks[1].ask && ticks[0].bid==ticks[1].bid) index=0; //--- Wenn Bid-Preis gestiegen else if(ticks[0].bid>ticks[1].bid) index=1; //--- Wenn Bid-Preis gefallen else if(ticks[0].bid<ticks[1].bid) index=2; //--- Setzen des entsprechenden Icons m_canvas_table.ChangeImage(c,r,index,true); } else { //--- Spalte der Preisdifferenzen - Spread (!) if(c==3) { //--- Ermitteln des Spread in Points int spread=(int)::SymbolInfoInteger(symbol,SYMBOL_SPREAD); m_canvas_table.SetValue(c,r,string(spread),true); continue; } //--- Ermitteln der Dezimalstellen int digit=(int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS); //--- Spalte der Bid-Preise if(c==1) { m_canvas_table.SetValue(c,r,::DoubleToString(ticks[0].bid,digit)); //--- Farbauswahl bei einer Preisänderung gemäß Änderungsrichtung if(ticks[0].bid!=ticks[1].bid) m_canvas_table.TextColor(c,r,(ticks[0].bid<ticks[1].bid)? clrRed : clrBlue,true); //--- continue; } //--- Spalte der Ask-Preise if(c==2) { m_canvas_table.SetValue(c,r,::DoubleToString(ticks[0].ask,digit)); //--- Farbauswahl bei einer Preisänderung gemäß Änderungsrichtung if(ticks[0].ask!=ticks[1].ask) m_canvas_table.TextColor(c,r,(ticks[0].ask<ticks[1].ask)? clrRed : clrBlue,true); //--- continue; } //--- Spalte der Zeit der zuletzt erhaltenen Kurse des Symbols if(c==4) { long time =::SymbolInfoInteger(symbol,SYMBOL_TIME); string time_msc =::IntegerToString(ticks[0].time_msc); int length =::StringLen(time_msc); string msc =::StringSubstr(time_msc,length-3,3); string str =::TimeToString(time,TIME_MINUTES|TIME_SECONDS)+"."+msc; //--- color clr=clrBlack; //--- Wenn keine Preisänderung if(ticks[0].ask==ticks[1].ask && ticks[0].bid==ticks[1].bid) clr=clrBlack; //--- Wenn Bid-Preis gestiegen else if(ticks[0].bid>ticks[1].bid) clr=clrBlue; //--- Wenn Bid-Preis gefallen else if(ticks[0].bid<ticks[1].bid) clr=clrRed; //--- Setzen des Textes und der Textfarbe m_canvas_table.SetValue(c,r,str); m_canvas_table.TextColor(c,r,clr,true); continue; } } } } //--- Aktualisieren der Tabelle m_canvas_table.UpdateTable(); }
Und so sieht das dann aus:
Fig. 5. Datenvergleich zwischen dem Datenfenster und unserer Tabelle.
Die Testanwendung dieses Artikels kann mittel des Links unten heruntergeladen werden, für ein weiteres Studium.
Schlussfolgerung
Das Schema der Bibliothek des grafischen Interfaces im aktuellen Entwicklungsstand schaut aus wie unten abgebildet.
Fig. 6. Struktur der Bibliothek im augenblicklichen Entwicklungszustand.
Unten können Sie die letzten Versionen der Bibliothek und der Dateien zum Testen herunterladen.
Bei Fragen zur Verwendung des bereitgestellten Materials, können Sie auf die detaillierten Beschreibungen im Lauf der Entwicklung der Bibliothek in den vorangegangenen Artikeln dieser Serie zurückgreifen oder Sie stellen Ihre Fragen im Kommentarteil diese Artikels.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/3042





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.