Grafische Interfaces X: Das Standard Chart-Steuerelement (Build 4)
Anatoli Kazharski | 11 Januar, 2017
Inhalt
- Einleitung
- Entwicklung einer Klasse für die Erstellung des Standard Chart-Steuerelementes
- Anwendung zum Testen des Steuerelements
- Optimierung der Timer und Event-Handler der Bibliotheks-Engine
- Optimierung der Strukturansicht und der Controls des Datei-Navigators
- Neue Icons für Verzeichnisse und Dateien in dem Datei-Navigator
- Schlussfolgerung
Einleitung
Der erste Artikel Grafisches Interface I: Vorbereiten der Bibliotheksstruktur (Kapitel 1) Beschreibt im Detail wofür diese Bibliothek gedacht ist. Am Ende von jedem Kapitel, finden Sie eine vollständige Liste mit Links zu diesem Artikel. Zudem finden Sie dort eine Möglichkeit das Projekt, entsprechend dem aktuellen Entwicklungsstand, herunterzuladen. Die Dateien müssen in den gleichen Verzeichnissen untergebracht werden, so, wie Sie auch in dem Archiv abgelegt sind.
Betrachten Sie wir ein anderes Steuerelement, das in der Bibliothek nicht feheln darf. Beim Start des Trading-Terminals, wird der Kurs-Chart für den Benutzer geöffnet Es wäre praktisch, wenn wir ein Werkzeug hätten, welches es uns ermöglicht, die Charts viel leichter verwalten zu können. Ein vorherige Artikel mit dem Namen MQL5 Cookbook: Monitoring Multiple Time Frames in a Single Window hat eine mögliche Variante eines solchen Tools demonstriert. Dieses Mal schreiben wir eine Klasse zum Erstellen eines Steuerelements, das einfach und bedienungsfreundlich in grafischen Benutzeroberflächen von Benutzerdefinierten MQL Anwendungen verwendet werden kann. Im Gegensatz zu der vorherigen unter dem oben aufgezeigten link erhältlichen Version, wird diese Implementation es ermöglichen, den Inhalt der Subcharts horizontal synchron zum Hauptfenster zu scrollen.
Darüber hinaus werden wir weiterhin den Bibliotheks-Code dahingehend optimieren, damit die CPU-Last verringert wird. Weitere Details werden in dem nachfolgenden Artikel ausgeführt.
Entwicklung einer Klasse für die Erstellung des Standard Chart-Steuerelementes
Bevor wir mit der Entwicklung der CStandardChart Klasse für die Erzeugung des Standard Chart controls fortfahren, muss die CSubChart Basisklasse mit zusätzlichen Eigenschaften (Sehen Sie sich dazu das nachfolgende Listing an) in der Object.mqh Datei ausgestattet werden. Dieses haben wir zuvor auch schon mit allen anderen Typen von grafischen Objekten, während der Entwicklung der Controls für diese Bibliothek, gemacht. Die Basisklasse für die CSubChart Klasse ist eine Klasse aus der Standardbibliothek — CChartObjectSubChart.
//+---------------------------------------------------------------- //| Objects.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+---------------------------------------------------------------- ... //--- Liste der Klassen in der Datei für eine schnelle Navigation (Alt+G) ... class CSubChart; //+---------------------------------------------------------------- //| Klasse mit zusätzlichen Eigenschaften für das Subchart-Objekt | //+---------------------------------------------------------------- class CSubChart : public CChartObjectSubChart { protected: int m_x; int m_y; int m_x2; int m_y2; int m_x_gap; int m_y_gap; int m_x_size; int m_y_size; bool m_mouse_focus; //--- public: CSubChart(void); ~CSubChart(void); //--- Koordinaten int X(void) { return(m_x); } void X(const int x) { m_x=x; } int Y(void) { return(m_y); } void Y(const int y) { m_y=y; } int X2(void) { return(m_x+m_x_size); } int Y2(void) { return(m_y+m_y_size); } //--- Abstände von dem Eckpunkt(xy) int XGap(void) { return(m_x_gap); } void XGap(const int x_gap) { m_x_gap=x_gap; } int YGap(void) { return(m_y_gap); } void YGap(const int y_gap) { m_y_gap=y_gap; } //--- Größe int XSize(void) { return(m_x_size); } void XSize(const int x_size) { m_x_size=x_size; } int YSize(void) { return(m_y_size); } void YSize(const int y_size) { m_y_size=y_size; } //--- Focus bool MouseFocus(void) { return(m_mouse_focus); } void MouseFocus(const bool focus) { m_mouse_focus=focus; } }; //+---------------------------------------------------------------- //| Konstruktor | //+---------------------------------------------------------------- CSubChart::CSubChart(void) : m_x(0), m_y(0), m_x2(0), m_y2(0), m_x_gap(0), m_y_gap(0), m_x_size(0), m_y_size(0), m_mouse_focus(false) { } //+---------------------------------------------------------------- //| Destruktor | //+---------------------------------------------------------------- CSubChart::~CSubChart(void) { }
Die CChartObjectSubChart Klasse enthält die Methoden für das Erzeugen des Subcharts, sowie auch alle Methoden für das Modifizieren der am häufigsten verwendeten Eigenschaften eines Charts. Die Liste enthält Methoden für das setzen und Abfragen der Eigenschaften wie:
- Koordinaten und Größenangaben;
- Symbol, Timeframe und Skalierung;
- Darstellung der Preis und Zeit Skalas.
//+---------------------------------------------------------------- //| ChartObjectSubChart.mqh | //| Copyright 2009-2013, MetaQuotes Software Corp. | //| http://www.mql5.com | //+---------------------------------------------------------------- #include "ChartObject.mqh" //+---------------------------------------------------------------- //| Class CChartObjectSubChart. | //| Verwendung: Klasse für das "SubChart" Object des Charts. | //| Abgeleitet von der Klasse CChartObject. | //+---------------------------------------------------------------- class CChartObjectSubChart : public CChartObject { public: CChartObjectSubChart(void); ~CChartObjectSubChart(void); //--- Methoden für den Zugriff auf die Eigenschaften des Objektes int X_Distance(void) const; bool X_Distance(const int X) const; int Y_Distance(void) const; bool Y_Distance(const int Y) const; ENUM_BASE_CORNER Corner(void) const; bool Corner(const ENUM_BASE_CORNER corner) const; int X_Size(void) const; bool X_Size(const int size) const; int Y_Size(void) const; bool Y_Size(const int size) const; string Symbol(void) const; bool Symbol(const string symbol) const; int Period(void) const; bool Period(const int period) const; int Scale(void) const; bool Scale(const int scale) const; bool DateScale(void) const; bool DateScale(const bool scale) const; bool PriceScale(void) const; bool PriceScale(const bool scale) const; //--- Das Ändern der Zeit und Preis Koordinaten ist blockiert bool Time(const datetime time) const { return(false); } bool Price(const double price) const { return(false); } //--- Methoden für das Erzeugen des Objektes bool Create(long chart_id,const string name,const int window, const int X,const int Y,const int sizeX,const int sizeY); //--- Methoden für die Identifizierung des Objektes virtual int Type(void) const { return(OBJ_CHART); } //--- Methoden für das Arbeiten mit Dateien virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); };
Nun können wir die StandardChart.mqh Datei mit der CStandardChart Klasse erzeugen, in welcher die Standardmethoden für alle bibliotheks Controls angegeben werden, wie es in den nachfolgenden Programmcode gezeigt wird. Da das Steuerelement über einen horizontalen Scroll-Modus verfügen wird, benötigen wir ein Symbol für den Maus-Cursor, der dem Benutzer anzeigt, dass der scrolling Modus aktiviert ist und die Daten in den Subcharts verschoben werden, da sich der Mauszeiger horizontal bewegt hat. Um das Icon verändern zu können, beziehen wir die Pointer.mqh Datei, welche die CPointer Klasse beinhaltet mit ein.Diese Klasse haben wir zuvor in dem Artikel Graphische Interfaces VIII: Das Baumasicht-Control (Kapitel 2) besprochen. Als Symbol für den Mauszeiger verwenden wir eine Kopie des aktiviem Zeigers mit dem für das horizontale Scrollen der Hauptcharts (doppelseitiger schwarzer Pfeil mit einem weißen Umriss). Am Ende des Artikels finden die finden Sie zwei Versionen von diesem Icon (schwarze and blaue Pfeile).
Dementsprechend wurde die Enumeration des Mauszeigers (ENUM_MOUSE_POINTER) um einen weiteren Bezeichner ergänzt(MP_X_SCROLL):
//+---------------------------------------------------------------- //| Enumeration der Pointer-Typen | //+---------------------------------------------------------------- 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_SCROLL =5 };
Zusätzlich ist es notwendig die Resourcen mit diesen Icons für diesen Typ von Mauszeigern in der Pointer.mqh Datei mit einzubeziehen, und die Schalter(switch)-Konstruktion in der CPointer::SetPointerBmp() Methode sollte um einem weiteren Case-Block erweitert werden.:
//+---------------------------------------------------------------- //| Pointer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+---------------------------------------------------------------- #include "Element.mqh" //--- Resources ... #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll_blue.bmp" //+---------------------------------------------------------------- //| Festlegen der Cursor-Bilder entsprechend des Cursor-Typs | //+---------------------------------------------------------------- void CPointer::SetPointerBmp(void) { switch(m_type) { case MP_X_RESIZE : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs.bmp"; break; case MP_Y_RESIZE : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs.bmp"; break; case MP_XY1_RESIZE : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_xy1_rs_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_xy1_rs.bmp"; break; case MP_XY2_RESIZE : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_xy2_rs_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_xy2_rs.bmp"; break; case MP_X_SCROLL : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll.bmp"; break; } //--- Falls dir benutzerdefinierte Typ (MP_CUSTOM) angegeben ist if(m_file_on=="" || m_file_off=="") ::Print(__FUNCTION__," > Both images must be set for the cursor!"); }
Es sollte auch beachtet werden, dass die Moving() Methode in zwei Modi verwendet werden kann, was über das dritte Argument der Methode festgelegt wird. Der Standardwert für dieses Argument ist false, was bedeutet, dass ein Control nur bewegt werden kann, falls das Formular, zu welchem es hinzugefügt worden ist, sich ebenfalls in dem Bewegungsmodus befindet.
//+---------------------------------------------------------------- //| StandardChart.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+---------------------------------------------------------------- #include "Element.mqh" #include "Window.mqh" #include "Pointer.mqh" //+---------------------------------------------------------------- //| Klasse für die Erzeugung eines Standard-Charts | //+---------------------------------------------------------------- class CStandardChart : public CElement { private: //--- Ein Pointer zu der Form zu welchem das Element hinzugefügt worden ist CWindow *m_wnd; //--- public: //--- Speichert den Pointer void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } //--- public: //--- Chart Eventhandler virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- Bewegen des Elementes virtual void Moving(const int x,const int y,const bool moving_mode=false); //--- (1) Anzeigen, (2) verstecken, (3) zurücksetzen, (4) löschen virtual void Show(void); virtual void Hide(void); virtual void Reset(void); virtual void Delete(void); //--- (1) Setzen, (2) Zurücksetzen der Prioritäten der linken Maustaste virtual void SetZorders(void); virtual void ResetZorders(void); //--- private: //--- Veränderung der Breite an der rechten Ecke des Fensters virtual void ChangeWidthByRightWindowSide(void); //--- Verändern der Höhe an der unteren Ecke des Fensters virtual void ChangeHeightByBottomWindowSide(void); }; //+---------------------------------------------------------------- //| Konstruktor | //+---------------------------------------------------------------- CStandardChart::CStandardChart(void) { //--- Abspeichern des namens der Elementklasse in der Basisklasse CElement::ClassName(CLASS_NAME); } //+---------------------------------------------------------------- //| Destruktor | //+---------------------------------------------------------------- CStandardChart::~CStandardChart(void) { }
Falls der Wert des dritten Argumentes in der Moving() Methode auf true gesetzt wird, dann wird das Control auf jeden Fall bewegt, unabhängig davon, ob sich das Formular in dem Bewegungsmodus befindet. In einigen Fällen kann dieses die CPU-Auslastung signifikant reduzieren.
Um festzustellen zu können, ob sich das Formular im Bewegungsmodus befindet, wurde die ClampingAreaMouse() Methode zu der CWindow Klasse hinzugefügt. Sie gibt den Bereich, wo die linke Maustaste gedrückt worden war, zurück:
//+---------------------------------------------------------------- //| Klasse für das Erzeugen einer Form mit Controls | //+---------------------------------------------------------------- class CWindow : public CElement { public: //--- Gib den Bereich zurück wo die linke Maustaste gedrückt wurde ENUM_MOUSE_STATE ClampingAreaMouse(void) const { return(m_clamping_area_mouse); } };
Die CWindow::ClampingAreaMouse() Methode kann nur über den Formular-Pointer in jedem hinzugefügten Control zu dieser Form aufgerufen werden. Damit alles so funktioniert, wie wir es oben beschrieben haben, muss noch ein Programm Block innerhalb der Moving() Methode eines jedem Controls hinzugefügt werden, so wie es das nachfolgende Listing zeigt. (Sehen Sie sich dazu das in Gelb markierte Fragment an). Als Beispiel wird hier die Methode der CSimpleButton (kurze version) Klasse gezeigt.
//+---------------------------------------------------------------- //| Bewegen der Controls | //+---------------------------------------------------------------- void CSimpleButton::Moving(const int x,const int y,const bool moving_mode=false) { //--- Abbrechen, falls das Element versteckt ist if(!CElement::IsVisible()) return; //--- Falls die Verwaltung auf das Fenster gerichtet ist, Identifizierung der Position if(!moving_mode) if(m_wnd.ClampingAreaMouse()!=PRESSED_INSIDE_HEADER) return; //--- Wenn der Ankerpunkt die rechte Seite ist //--- If the anchored to the left //--- Wenn der Ankerpunkt unten ist //--- If the anchored to the top //--- Aktualisieren der Koordinaten in den grafischen Objekten m_button.X_Distance(m_button.X()); m_button.Y_Distance(m_button.Y()); }
Ein Beispiel für die Verwendung der Moving() Methode kann in den nachfolgenden listing angesehen werden, welche den Code der CSimpleButton::Show() Methode demonstriert. In diesem Fall werden die Control-Koordinaten gewaltsam aktualisiert, daher wurde true als drittes Argument übergeben. Entsprechende Änderungen wurden in allen Klassen der Bibliothek vorgenommen, welche die Moving() Methode verwenden.
//+---------------------------------------------------------------- //| Zeigt den Button | //+---------------------------------------------------------------- void CSimpleButton::Show(void) { //--- Verlassen, falls das Element bereits sichtbar ist if(CElement::IsVisible()) return; //--- Alle Objekte sichtbar machen m_button.Timeframes(OBJ_ALL_PERIODS); //--- Status der Sichtbarkeit CElement::IsVisible(true); //--- Aktualisiere die Positionen der Objekte Moving(m_wnd.X(),m_wnd.Y(),true); }
Die Optimierung der entwickelten Bibliothek werden wir später in einem Artikel besprechen. Jetzt werden wir erstmal ein Standard Chart-Control besprechen.
Lassen Sie uns nun die Möglichkeit schaffen, eine Serie von Sub-Charts in einer Reihe platzieren zu können. Dafür ist es notwendig, dynamische Arrays von Objekten zu deklarieren, welche Charts repräsentieren, so wie bestimmte Eigenschaften, wie zum Beispiel: (1) Chart Bezeichner, (2) Symbol und (3) Timeframe. Bevor wir das Standard Chart control erzeugen, ist es notwendig die CStandardChart::AddSubChart() Methode zu verwenden, in welcher das Chart-Symbol und die Timeframe angegeben werden. Zu Beginn dieser Methode, bevor Elemente zu dem Array hinzugefügt werden und diese mit den übergebenen Parametern initialisiert werden, gibt es noch eine Überprüfung über die Verfügbarkeit des Symbols unter Verwendung der CStandardChart::CheckSymbol() Methode.
class CStandardChart : public CElement { private: //--- Objekte für die Erzeugung des Elementes CSubChart m_sub_chart[]; //--- Chart-Eigenschaften: long m_sub_chart_id[]; string m_sub_chart_symbol[]; ENUM_TIMEFRAMES m_sub_chart_tf[]; //--- public: //--- Fügt vor der Erzeugung einen Chart mit den angegebenen Parameter hinzu void AddSubChart(const string symbol,const ENUM_TIMEFRAMES tf); //--- private: //--- Überprüfung des Symbols bool CheckSymbol(const string symbol); };
Die CStandardChart::CheckSymbol() Methode überprüft zunächst, ob das angegebene Symbol in dem MarketWatch-Window verfügbar ist. Wenn das Symbol nicht gefunden wird, wird ein Versuch unternommen das Symbol in der allgemeinen Liste zu finden. Wenn das Symbol gefunden wird, muss es zu der MarketWatch-Liste hinzugefügt werden. Andernfalls wird es nicht möglich sein den Subchart mit diesem Symbol zu erzeugen (Stattdessen wird ein Sub-Chart mit dem Symbol des Haupt-Charts erzeugt).
Im Erfolgsfall gibt die CStandardChart::CheckSymbol() Methode true zurück. Falls das Symbol nicht gefunden wurde gibt die Method false zurück, Und der Subchart wird nicht hinzugefügt (Arrays haben die gleiche Größe), und es wird darüber eine Nachricht in dem Journal ausgegeben.
//+---------------------------------------------------------------- //| Fügt einen Chart hinzu | //+---------------------------------------------------------------- void CStandardChart::AddSubChart(const string symbol,const ENUM_TIMEFRAMES tf) { //--- Prüft, ob ein Symbol auf dem Server verfügbar ist if(!CheckSymbol(symbol)) { ::Print(__FUNCTION__," > Symbol "+symbol+" is not available on the server!"); return; } //--- Vergrößern des Arrays um ein Element int array_size=::ArraySize(m_sub_chart); int new_size=array_size+1; ::ArrayResize(m_sub_chart,new_size); ::ArrayResize(m_sub_chart_id,new_size); ::ArrayResize(m_sub_chart_symbol,new_size); ::ArrayResize(m_sub_chart_tf,new_size); //--- Abspeichern des Wertes des übergebenen Parameters m_sub_chart_symbol[array_size] =symbol; m_sub_chart_tf[array_size] =tf; } //+---------------------------------------------------------------- //| Überprüfung der Verfügbarkeit eines Symbols | //+---------------------------------------------------------------- bool CStandardChart::CheckSymbol(const string symbol) { bool flag=false; //--- Überprüfung des Symbols in den MarketWatch window int symbols_total=::SymbolsTotal(true); for(int i=0; i<symbols_total; i++) { //--- Wenn das Symbol verfügbar ist, Ende des Zyklus if(::SymbolName(i,true)==symbol) { flag=true; break; } } //--- Wenn das Symbol im Fenster "Market Watch" nicht verfügbar ist if(!flag) { //---... versuch es in der allgemeinen Liste zu finden symbols_total=::SymbolsTotal(false); for(int i=0; i<symbols_total; i++) { // ---Wenn dieses Symbol verfügbar ist if(::SymbolName(i,false)==symbol) { //---... füge es dem Marktbeobachtungsfenster hinzu und beende den Zyklus ::SymbolSelect(symbol,true); flag=true; break; } } } //--- gib die Suchergebnisse zurück return(flag); }
Das Erstellen eines Standard-Chart-Steuerelements erfordert drei Methoden: Eine übergeordnete public Methode und zwei private Methoden, eine von denen bezieht sich auf das Symbol für den Maus-cursor im horizontalen Scroll-Modus. Die CStandardChart::SubChartsTotal() public Methode wurde als eine zusätzliche Methode zur Klasse hinzugefügt, um die Anzahl der Unterdiagramm abrufen zu können.
class CStandardChart : public CElement { private: //--- Objekte für die Erzeugung des Elementes CSubChart m_sub_chart[]; CPointer m_x_scroll; //--- public: //--- Methoden zum Erstellen einer Stndard-Charts bool CreateStandardChart(const long chart_id,const int subwin,const int x,const int y); //--- private: bool CreateSubChart(void); bool CreateXScrollPointer(void); //--- public: //--- Gibt die Größe des Arrays für Charts zurück int SubChartsTotal(void) const { return(::ArraySize(m_sub_chart)); } };
Betrachten Sie wir die CStandardChart::CreateSubCharts()-Methode zum Erstellen von Unterdiagrammen. Hier findet eine Überprüfung der Anzahl der Charts in dem Array statt, die vor der Erzeugung des Controls zu Beginn der Methode hinzugefügt wurden. Falls keine hinzugefügt wurden, dann beendet das Programm an dieser Stelle die Methode und schreibt eine entsprechende Nachricht in das Journal.
Wenn ein Chart hinzugefügt worden ist, dann wird die Breite für jedes Objekt berechnet. Die Gesamtbreite eines Controls muss von dem User in der benutzerdefinierten Klasse der MQL Anwendung definiert werden. Falls es notwendig ist mehrere Charts zu erzeugen, dann ist es ausreichend die gesamte Breite des Controls mit der Anzahl der Charts zu dividieren, um die Breite für jedes einzelne Objekt zu erhalten.
Anschließend werden die Objekte innerhalb einer Schleife erzeugt. Hierbei wird auch die Positionierung berücksichtigt. (Ankerpunkte zu einer der Seiten des Formulars). Dieser Punkt wurde schon in den vorherigen Artikel beschrieben und wird daher hier nicht weiter besprochen.
Nach der Erzeugung des Subcharts, muss derBezeichne des erstellten Charts abgefragt und in dem Array gespeichert werden. Ebenso müssen seine Eigenschaften gesetzt und gespeichert werden.
//+---------------------------------------------------------------- //| Erzeugen der Charts | //+---------------------------------------------------------------- bool CStandardChart::CreateSubCharts(void) { //--- Abfrage der Anzahl der Charts int sub_charts_total=SubChartsTotal(); //--- Meldung ausgeben, falls es in dieser Gruppe keinen Chart gibt if(sub_charts_total<1) { ::Print(__FUNCTION__," > This method is to be called, " "if a group contains at least one chart! Use the CStandardChart::AddSubChart() method"); return(false); } //--- Berechnung der Koordinaten und der Größen int x=m_x; int x_size=(sub_charts_total>1)? m_x_size/sub_charts_total : m_x_size; //--- Erzeugen der angegebenen Anzahl von Charts for(int i=0; i<sub_charts_total; i++) { //--- Den Objektnamen bilden string name=CElement::ProgramName()+"_sub_chart_"+(string)i+"__"+(string)CElement::Id(); //--- Berechnung der x-Koordinate x=(i>0)?(m_anchor_right_window_side)? x-x_size+1 : x+x_size-1 : x; //--- Die Breite des letzten Charts einstellen if(i+1>=sub_charts_total) x_size=m_x_size-(x_size*(sub_charts_total-1)-(sub_charts_total-1)); //--- Einen Button einrichten if(!m_sub_chart[i].Create(m_chart_id,name,m_subwin,x,m_y,x_size,m_y_size)) return(false); //--- Abfragen und Abspeichern des Bezeichners des erzeugten Charts m_sub_chart_id[i]=m_sub_chart[i].GetInteger(OBJPROP_CHART_ID); //--- Setzen der Eigenschaften m_sub_chart[i].Symbol(m_sub_chart_symbol[i]); m_sub_chart[i].Period(m_sub_chart_tf[i]); m_sub_chart[i].Z_Order(m_zorder); m_sub_chart[i].Tooltip("\n"); //--- Abspeichern der Größe m_sub_chart[i].XSize(x_size); m_sub_chart[i].YSize(m_y_size); //--- Ränder von den Kanten m_sub_chart[i].XGap((m_anchor_right_window_side)? x : x-m_wnd.X()); m_sub_chart[i].YGap((m_anchor_bottom_window_side)? m_y : m_y-m_wnd.Y()); //--- Abspeichern des Objekt-Pointers CElement::AddToArray(m_sub_chart[i]); } //--- return(true); }
Es ist auch nachträglich noch möglich alle Eigenschaften eines Sub-Charts innerhalb des Standard Charts zu verändern. Dieses geschieht mithilfe des Pointers, welche über die Methode CStandardChart::GetSubChartPointer() abgerufen werden kann. Falls aus Versehen ein falscher Index übergeben wurde, wird dieser korrigiert um eine Überschreitung der Grenzen des Arrays zu vermeiden.
class CStandardChart : public CElement { public: //--- Gibt den Pointer zu dem Chart über den angegebenen Index zurück CSubChart *GetSubChartPointer(const uint index); }; //+---------------------------------------------------------------- //| Gibt den Pointer zu dem Chart über den angegebenen Index zurück | //+---------------------------------------------------------------- CSubChart *CStandardChart::GetSubChartPointer(const uint index) { uint array_size=::ArraySize(m_sub_chart); //--- Falls es keinen Chart gibt, dann benachrichtigen if(array_size<1) { ::Print(__FUNCTION__," > This method is to be called, " "if a group contains at least one chart!"); } //--- Korrekturen, falls die Größe überschritten wird uint i=(index>=array_size)? array_size-1 : index; //--- Rückgabe des Pointers return(::GetPointer(m_sub_chart[i])); }
Es wird nur dann ein Icon für den Mauszeiger erzeugt, falls der horizontale Scroll-Modus für die Daten in den SubCharts aktiviert ist. Dieser sollte mit Hilfe der Methode CStandardChart::XScrollMode() vor der Erzeugung des Controls aktiviert werden.
class CStandardChart : public CElement { private: //--- Horizontales Scroll-Modus bool m_x_scroll_mode; //--- public: //--- Horizontales Scroll-Modus void XScrollMode(const bool mode) { m_x_scroll_mode=mode; } }; //+---------------------------------------------------------------- //| Erzeugen des Mauszeigers für das horizontale Scrollen | //+---------------------------------------------------------------- bool CStandardChart::CreateXScrollPointer(void) { //--- Abbrechen, falls das horizontale Scrollen nicht gebraucht wird if(!m_x_scroll_mode) return(true); //--- Festlegen der Eigenschaften m_x_scroll.XGap(0); m_x_scroll.YGap(-20); m_x_scroll.Id(CElement::Id()); m_x_scroll.Type(MP_X_SCROLL); //--- Erzeugung des Elementes if(!m_x_scroll.CreatePointer(m_chart_id,m_subwin)) return(false); //--- return(true); }
Zusammenfassung. Falls der horizontales Scrollen muss aktiviert wurde, dann verwendet diese Operation die CStandardChart::HorizontalScroll() Methode, welche in dem Eventhandler des Controls aufgerufen wird, sobald das CHARTEVENT_MOUSE_MOVE Event ausgelöst wird. Falls die linke Maustaste gedrückt ist, wird, in Abhängigkeit ob es sich um einen aktuellen Mausklick handelt oder ob es sich um sich wiederholende Aufrufe zu der Methode während des Prozesses des horizontalen Scrollens handelt, der Abstand vom ersten Klick und der aktuell zurückgelegten Strecke in Pixeln berechnet. Hier:
- Das Formular ist gesperrt.
- Die Koordinaten für das Icon des Mauszeigers werden berechnet.
- Das Icon wird angezeigt.
Der berechnete Wert für das Verschieben der Daten in den Sub-Charts kann negativ sein, da der Offset relativ zu der letzten Bar berechnet wird - Die::ChartNavigate() Methode mit dem CHART_END Wert (Das zweite Argument) aus der ENUM_CHART_POSITION Enumeration. Falls der Wert positiv ist, verlässt das Programm die Methode. Wenn die Überprüfungen erfolgreich abgeschlossen wurde, dann wird der aktuelle Wert für die Verschiebung für den nächsten Durchlauf abgespeichert . Anschließend werden die "Auto Scroll" (CHART_AUTOSCROLL) und "Chart shift from the right border" (CHART_SHIFT) in allen Sub-Charts deaktiviert und die Verschiebung wird entsprechend des berechneten Wertes durchgeführt.
Wenn die Maustaste losgelassen wird, dann wird das Formular entsperrt und das Icon des Mauszeigers, welches den Prozess der horizontalen Verschiebung anzeigt, wird versteckt. Anschließend verlässt das Programm die Methode.
class CStandardChart : public CElement { private: //--- Die Variablen für das horizontale Scrollen des Charts int m_prev_x; int m_new_x_point; int m_prev_new_x_point; //--- private: //--- Horizontales Scrolling void HorizontalScroll(void); }; //+---------------------------------------------------------------- //| Horizontales Scrollen des Charts | //+---------------------------------------------------------------- void CStandardChart::HorizontalScroll(void) { //--- Abbrechen, falls das horizontale Scrollen des Charts deaktiviert ist if(!m_x_scroll_mode) return; //--- Falls die Maustaste gedrückt wurde if(m_mouse.LeftButtonState()) { //--- Abspeichern der aktuellen x-Koordinate des Mauszeigers if(m_prev_x==0) { m_prev_x =m_mouse.X()+m_prev_new_x_point; m_new_x_point =m_prev_new_x_point; } else m_new_x_point=m_prev_x-m_mouse.X(); //--- Blockieren der Form if(!m_wnd.IsLocked()) { m_wnd.IsLocked(true); m_wnd.IdActivatedElement(CElement::Id()); } //--- Aktualisiere die Koordinaten des Pointers und mache sie sichtbar int l_x=m_mouse.X()-m_x_scroll.XGap(); int l_y=m_mouse.Y()-m_x_scroll.YGap(); m_x_scroll.Moving(l_x,l_y); //--- Zeige die Pointer m_x_scroll.Show(); //--- Setze das visibility(sichtbarkeist) flag m_x_scroll.IsVisible(true); } else { m_prev_x=0; //--- Die Form entsperren if(m_wnd.IdActivatedElement()==CElement::Id()) { m_wnd.IsLocked(false); m_wnd.IdActivatedElement(WRONG_VALUE); } //--- Verstecke den Pointer m_x_scroll.Hide(); //--- Setze das visibility(sichtbarkeist) flag m_x_scroll.IsVisible(false); return; } //--- Abbrechen, falls wir einen positiven Wert haben if(m_new_x_point>0) return; //--- Abspeichern der aktuellen Position m_prev_new_x_point=m_new_x_point; //--- Allen Charts zuweisen int symbols_total=SubChartsTotal(); //--- Deaktivierung des Autoscroll-Modus und der Verschiebung von der rechten Seite for(int i=0; i<symbols_total; i++) { if(::ChartGetInteger(m_sub_chart_id[i],CHART_AUTOSCROLL)) ::ChartSetInteger(m_sub_chart_id[i],CHART_AUTOSCROLL,false); if(::ChartGetInteger(m_sub_chart_id[i],CHART_SHIFT)) ::ChartSetInteger(m_sub_chart_id[i],CHART_SHIFT,false); } //--- Zurücksetzen der letzten Fehlermeldung ::ResetLastError(); //--- Verschiebe die Charts for(int i=0; i<symbols_total; i++) if(!::ChartNavigate(m_sub_chart_id[i],CHART_END,m_new_x_point)) ::Print(__FUNCTION__," > error: ",::GetLastError()); }
Die CStandardChart::ZeroHorizontalScrollVariables() Methode wird dafür benutzt, die Hilfsvariablen des horizontalen Scroll-Modus für die Sub-Charts zurückzusetzen. Es kann auch erforderlich sein bis zur letzten war programmatisch vorzugehen. In diesem Fall wird die CStandardChart::ResetCharts() Methode verwendet.
class CStandardChart : public CElement { public: //--- Zurücksetzen des Charts void ResetCharts(void); //--- private: //--- Zurücksetzen der Variablen für das horizontale Scrollen void ZeroHorizontalScrollVariables(void); }; //+---------------------------------------------------------------- //| Zurücksetzen der Charts | //+---------------------------------------------------------------- void CStandardChart::ResetCharts(void) { int sub_charts_total=SubChartsTotal(); for(int i=0; i<sub_charts_total; i++) ::ChartNavigate(m_sub_chart_id[i],CHART_END); //--- Zurücksetzen der Hilfsvariablen für das horizontale Scrollen der Charts ZeroHorizontalScrollVariables(); } //+---------------------------------------------------------------- //| Zurücksetzen der Variablen für das horizontale Scrollen | //+---------------------------------------------------------------- void CStandardChart::ZeroHorizontalScrollVariables(void) { m_prev_x =0; m_new_x_point =0; m_prev_new_x_point =0; }
Es könnte auch notwendig sein, ein Klick der linken Maustaste über einem Sub-Chart eines Standard-Charts nachzuverfolgen. Dafür fügen wir den ON_CLICK_SUB_CHART Bezeichner der Defines.mqh Datei hinzu:
//+---------------------------------------------------------------- //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+---------------------------------------------------------------- ... //--- Event Bezeichner ... #define ON_CLICK_SUB_CHART (28) // Clicking the subchart
Um bestimmen zu können, ob auf einen sub-Chart geklickt wurde, implementieren wir die CStandardChart::OnClickSubChart() Method. Wenn die Überprüfungen für den Namen und den Bezeichner erfolgreich abgeschlossen werden (Sehen Sie dazu das nachfolgende Listing), dann wird eine Nachricht generiert mit (1) demON_CLICK_SUB_CHART Bezeichner des Events, (2) dem Bezeichner des Controls, (3) dem Index des Sub-Charts und (4) dem Symbolnamen.
class CStandardChart : public CElement { private: //--- Verarbeitung eines Klicks auf den Sub-Chart bool OnClickSubChart(const string clicked_object); }; //+---------------------------------------------------------------- //| Verarbeiten eines Klicks auf einen Button | //+---------------------------------------------------------------- bool CStandardChart::OnClickSubChart(const string clicked_object) { //--- Abbrechen, falls der Klick nicht auf diesem Menüpunkt stattgefunden hat if(::StringFind(clicked_object,CElement::ProgramName()+"_sub_chart_",0)<0) return(false); //--- Abfragen des Bezeichners und des Indexes über den Objektnamen int id=CElement::IdFromObjectName(clicked_object); //--- Abbrechen, falls der Bezeichner nicht übereinstimmt if(id!=CElement::Id()) return(false); //--- Abfrage des Index int group_index=CElement::IndexFromObjectName(clicked_object); //--- Eine Nachricht darüber senden ::EventChartCustom(m_chart_id,ON_CLICK_SUB_CHART,CElement::Id(),group_index,m_sub_chart_symbol[group_index]); return(true); }
Angenommen, Sie benötigen eine andere Art der Navigation in Sub-Charts, ähnlich wie es für den Hauptchart mit den Mitteln des Terminals implementiert wird. Drückt man die Taste «Space» oder «Enter» im Terminal MetaTrader , dann wird ein Bearbeitungsfeld in der unteren linken Ecke des Diagramms aktiviert (siehe Screenshot unten). Dieses ist eine Art einer Befehlszeile, wo ein Datum eingegeben werden kann, um bis dahin auf dem Diagramm zu springen. Diese Befehlszeile kann auch verwendet werden, um das Symbol und den Zeitrahmen des Charts zu ändern.
Abbildung 1. Befehlszeile des Diagramms an der linken Ecke.
Übrigens, es wurde ein neues Feature für die Verwaltung der Befehlszeile in das neueste Update des Handelsterminals (build 1455) hinzugefügt.
…
8. MQL5: Die neue eigenschaft CHART_QUICK_NAVIGATION erlaubt das aktivieren und deaktivieren der schnellnavigation in den Charts. Falls Sie den Status dieser Eigenschaft ändern wollen, verwenden Sie die ChartSetInteger und ChartGetInteger Funktionen.
Die Navigations-Bar wird über einen Druck auf die Enter oder Space-Taste geöffnet. Diese erlaubt es Ihnen, sehr schnell zu einem bestimmten Datum innerhalb des Charts zu springen, sowie ein neues Symbol und eine neue Timeframe auszuwählen Falls ihr eigenes Programm diese Tasten abfragen sollte, dann deaktivieren Sie die Eigenschaft, CHART_QUICK_NAVIGATION damit es zu keinen Wechselwirkungen mit dem Terminal kommt. Die Schnellnavigation kann auch weiterhin durch einen Doppelklick geöffnet werden.
…
Innerhalb der grafischen Oberfläche kann alles bequemer und einfacher erfolgen. Die Easy And Fast Bibliothek beinhaltet bereits das Kalender Control (die CCalendar Klasse). Die Navigation in dem Hauptchart und dem Sub-Chart kann über die Auswahl eines Datums in dem Kalender implementiert werden. Lassen Sie uns alles bis auf eine einzige Methode mit einem Argument vereinfachen. Der Wert dieses Arguments ist das Datum, wohin der Chart verschoben werden soll. Wir nennen diese Methode CStandardChart::SubChartNavigate(). Der nachfolgende Programmtext zeigt die aktuelle Version dieser Methode.
Zu Beginn dieser Methode werden die "Auto Scroll" und "Shift from the right border of the chart" Modi des Haupt-Charts deaktiviert. Anschließen, wenn die Variable der Methode übergeben worden und größer ist, als der Anfang des aktuellen Tages, gehen wir einfach zu der letzten Bar und verlassen die Methode. Falls das Datum kleiner ist, dann ist es notwendig die Anzahl der Bars zu berechnen, um die nach links verschoben werden muss. Zunächst wird die Berechnung für den Hauptchart durchgeführt:
- Abfrage der gesamten Anzahl von Bars für das aktuelle Symbol und die aktuelle Timeframe vom Anfang des aktuellen Tages bis zu dem angegebenen Datum.
- Abfrage der sichtbaren Bars auf dem Chart
- Abfrage der Anzahl der Bars von Beginn des aktuellen Tages + zwei Bars als eine zusätzliche Einrückung.
- Berechnung der Anzahl der Bars für die Verschiebung von der letzten bar
Anschließend wird der Hauptchart nach links verschoben und das gleiche wird für alle Sub-Charts wiederholt.
class CStandardChart : public CElement { public: //--- Springe zu dem angegebenen Datum void SubChartNavigate(const datetime date); }; //+---------------------------------------------------------------- //| Springe zu dem angegebenen Datum | //+---------------------------------------------------------------- void CStandardChart::SubChartNavigate(const datetime date) { //--- (1) Das aktuelle Datum des Charts und (2) das neue in dem Kalender ausgewählte Datum datetime current_date =::StringToTime(::TimeToString(::TimeCurrent(),TIME_DATE)); datetime selected_date =date; //--- Deaktivierung des Autoscroll-Modus und der Verschiebung von der rechten Seite ::ChartSetInteger(m_chart_id,CHART_AUTOSCROLL,false); ::ChartSetInteger(m_chart_id,CHART_SHIFT,false); //--- Falls das ausgewählte Datum größer als das aktuelle Datum ist if(selected_date>=current_date) { //--- Gehe bei allen Charts zu dem aktuellen Datum ::ChartNavigate(m_chart_id,CHART_END); ResetCharts(); return; } //--- Abfrage der Anzahl der Bars von dem angegebenen Datum int bars_total =::Bars(::Symbol(),::Period(),selected_date,current_date); int visible_bars =(int)::ChartGetInteger(m_chart_id,CHART_VISIBLE_BARS); long seconds_today =::TimeCurrent()-current_date; int bars_today =int(seconds_today/::PeriodSeconds())+2; //--- Setzen des Abstandes von der rechten Seite für alle Charts m_prev_new_x_point=m_new_x_point=-((bars_total-visible_bars)+bars_today); ::ChartNavigate(m_chart_id,CHART_END,m_new_x_point); //--- int sub_charts_total=SubChartsTotal(); for(int i=0; i<sub_charts_total; i++) { //--- Deaktivierung des Autoscroll-Modus und der Verschiebung von der rechten Seite ::ChartSetInteger(m_sub_chart_id[i],CHART_AUTOSCROLL,false); ::ChartSetInteger(m_sub_chart_id[i],CHART_SHIFT,false); //--- Abfrage der Anzahl der Bars von dem angegebenen Datum bars_total =::Bars(m_sub_chart[i].Symbol(),(ENUM_TIMEFRAMES)m_sub_chart[i].Period(),selected_date,current_date); visible_bars =(int)::ChartGetInteger(m_sub_chart_id[i],CHART_VISIBLE_BARS); bars_today =int(seconds_today/::PeriodSeconds((ENUM_TIMEFRAMES)m_sub_chart[i].Period()))+2; //--- Abstand von der rechten Seite des Charts m_prev_new_x_point=m_new_x_point=-((bars_total-visible_bars)+bars_today); ::ChartNavigate(m_sub_chart_id[i],CHART_END,m_new_x_point); } }
Die Entwicklung der CStandardChart Klasse für die Erzeugung des Standard Chart Controls ist fertig. Lassen Sie uns nun eine Anwendung schreiben, um uns ansehen zu können wie es funktioniert.
Anwendung zum Testen des Steuerelements
Für den Test können Sie einen Expert Advisor aus dem vorherigen Artikel verwenden. Entfernen Sie alle Controls, mit Ausnahme des Hauptmenüs, der Statusbar und den Tabs. Machen Sie es so, dass jeder Tab seine separate Gruppe von Sub-Charts hat Jede Gruppe enthält eine bestimmte Währung; Somit wird auch jedes hat seine eigene Beschreibung/Beschriftung haben:
- Das erste Tab - EUR (Euro).
- Das zweite Tab - GBP (Great Britain Pound).
- Das dritte Tab - AUD (Australian Dollar).
- Das vierte Tab - CAD (Canadian Dollar).
- Das fünfte Tab - JPY (Japanese Yen).
Die Sub-Charts werden innerhalb des Arbeitsbereichs der Tabs gesetzt und sie werden automatisch in ihrer Größe verändert, wenn sich die Größe des Formulars ändert. Die rechte Seite des Arbeitsbereiches in den Tabs hat immer einen Abstand von 173 Pixeln von der rechten Kante des Formulars. Dieser Platz wird mit Controls für das Setzen von Eigenschaften wie
- Anzeige des zeitlichen Rahmens (Date time).
- Die Preisscala anzeigen(Price scale).
- Ändern der Charts-Timeframe (Timeframes).
- Navigation über den Kalender.
Als Beispiel ist es ausreichend, den Programmcode für die Erzeugung eines einzelnen Standard Charts zu zeigen (CStandardChart) Control. Denken Sie daran, dass das horizontale Scrollen eines Charts standardmäßig deaktiviert ist. Daher kann, falls es gebraucht wird, die CStandardChart::XScrollMode() Methode verwendet werden. Die CStandardChart::AddSubChart() Methode wird dazu verwendet, Charts der Gruppe hinzuzufügen.
//+---------------------------------------------------------------- //| Kasse für das erzeugende Anwendung | //+---------------------------------------------------------------- class CProgram : public CWndEvents { protected: //--- Standard chart CStandardChart m_sub_chart1; //--- protected: //--- Standard chart bool CreateSubChart1(const int x_gap,const int y_gap); }; //+---------------------------------------------------------------- //| Erzeuge Standard chart 1 | //+---------------------------------------------------------------- bool CProgram::CreateSubChart1(const int x_gap,const int y_gap) { //--- Abspeichern des Fenster-Pointers m_sub_chart1.WindowPointer(m_window); //--- Hinzufügen zum ersten Tab m_tabs.AddToElementsArray(0,m_sub_chart1); //--- Koordinaten int x=m_window.X()+x_gap; int y=m_window.Y()+y_gap; //--- Festlegen der Eigenschaften vor der Erzeugung m_sub_chart1.XSize(600); m_sub_chart1.YSize(200); m_sub_chart1.AutoXResizeMode(true); m_sub_chart1.AutoYResizeMode(true); m_sub_chart1.AutoXResizeRightOffset(175); m_sub_chart1.AutoYResizeBottomOffset(25); m_sub_chart1.XScrollMode(true); //--- Hinzufügen des Charts m_sub_chart1.AddSubChart("EURUSD",PERIOD_D1); m_sub_chart1.AddSubChart("EURGBP",PERIOD_D1); m_sub_chart1.AddSubChart("EURAUD",PERIOD_D1); //--- Erzeugung des Controls if(!m_sub_chart1.CreateStandardChart(m_chart_id,m_subwin,x,y)) return(false); //--- Hinzufügen des Objektes zu dem gemeinsamen Array von Objektgruppen CWndContainer::AddToElementsArray(0,m_sub_chart1); return(true); }
Der nachfolgende Screenshot zeigt das endgültige Ergebnis. In diesem Beispiel können die Daten des Sub-Charts horizontal gescrollt werden, so wie es auch in dem Hauptchart der Fall ist. Darüber hinaus funktioniert die Navigation zwischen den Sub-Charts über den Kalender, inklusive der schnellen weiterleitung der Daten.
Abbildung 2. Test des Standard Chart Controls.
Die Testanwendung, die in diesem Artikel vorgestellt wird, kann mit dem unten aufgeführten Link heruntergeladen werden.
Optimierung der Timer und Event-Handler der Bibliotheks-Engine
Zuvor haben wir die Easy And Fast Bibliothek nur in dem Windows 7 x64 Betriebssystem getestet . Nach einem Update zu Windows 10 x64, haben wir herausgefunden, dass die CPU Auslastung signifikant steigt. Diese Bibliothek verbrauchte bis zu 10% der CPU-Leistung, auch wenn sie sich im Standby-Modus befunden hat und es keine Interaktion mit dem grafischen Interface gab. Die nachfolgenden Screenshots zeigen die Belastung der CPU vor und nach dem Hinzufügen der Testanwendung zu dem Chart
Abbildung 3. Auslastung der CPU vor dem Hinzufügen der MQL Anwendung zu dem Chart.
Abbildung 4. Auslastung der CPU nach dem Hinzufügen der MQL Anwendung zu dem Chart.
Wir haben herausgefunden, dass das Problem in dem Timer der Bibliotheks-Engine liegt, der denChart alle 16ms updated, wie es in dem nachfolgenden Programmcode gezeigt wird:
//+---------------------------------------------------------------- //| Timer | //+---------------------------------------------------------------- void CWndEvents::OnTimerEvent(void) { //--- Verlassen, falls das Array leer ist if(CWndContainer::WindowsTotal()<1) return; //--- Überprüfen der Events von allen Elementen des Timers CheckElementsEventsTimer(); //--- Neuzeichnen des Charts m_chart.Redraw(); }
Die Auslastung der CPU steigt sogar noch weiter, wenn wir die Bewegung des Mauszeigers innerhalb des Arbeitsbereiches des Charts hinzufügen und Interaktionen mit dem grafischen Interface der MQL Anwendung ausführen. Unsere Aufgabe ist es nun die Operationen der Bibliotheks-Engine zu reduzieren und das wiederholte Neuzeichnen des Charts mit jedem Timer-Event bei einer Mausbewegung zu verhindern. Wie können wir das lösen?
Entfernen Sie die Zeile, die für das Neuzeichnen des Charts verantwortlich ist(Hervorgehoben in Rot) aus der CWndEvents::ChartEventMouseMove() Methode:
//+---------------------------------------------------------------- //| CHARTEVENT MOUSE MOVE event | //+---------------------------------------------------------------- void CWndEvents::ChartEventMouseMove(void) { //--- Abbrechen, wenn es sich nicht um eine Bewegung des Mauszeigers handelt if(m_id!=CHARTEVENT_MOUSE_MOVE) return; //--- Verschieben des Fensters MovingWindow(); //--- Festlegen des Chart-Status SetChartState(); //--- Neuzeichnen des Charts m_chart.Redraw(); }
Der Timer ist zurzeit verantwortlich für den Farbwechsel der Controls, wenn sich die Maus darüber befindet und das schnelle Vorspulen von verschiedenen Controls (Listen, Tabellen, Kalender etc) .Für diese Funktionen muss er konstant arbeiten. Um Ressourcen zu sparen, wird der Timer nur aktiviert, wenn der Mauszeiger anfängt sich zu bewegen und er wird deaktiviert, wenn die Bewegung des Mauszeigers stoppt.
Um diese Idee umzusetzen, müssen wir einige Dinge der CMouse Klasse hinzufügen. Zu diesen Ergänzungen gehört einen Zähler für die Aufrufe zu dem System Timer und der CMouse::GapBetweenCalls() Methode, welche die Differenz zwischen den Aufrufen zu dem Mauszeiger-Events zurück gibt.
class CMouse { private: //--- Counter für die Aufrufe ulong m_call_counter; //--- public: //--- Gibt (1) den Wert des Zählers der nach dem letzten Aufruf gespeichert wurde und(2) die Differenz zwischen den Aufrufen zu dem Eventhandler der Mauszeiger-Bewegung-Events zurück. ulong CallCounter(void) const { return(m_call_counter); } ulong GapBetweenCalls(void) const { return(::GetTickCount()-m_call_counter); } }; //+---------------------------------------------------------------- //| Konstruktor | //+---------------------------------------------------------------- CMouse::CMouse(void) : m_call_counter(::GetTickCount()) { }
Die Logik ist recht einfach. Sobald sich der Mauszeiger zu bewegen beginnt, speichert der Eventhandler der CMouse Klasse den aktuellen Wert des System.Timers:
//+---------------------------------------------------------------- //| Verarbeiten der Events von der Bewegung des Mauszeigers | //+---------------------------------------------------------------- void CMouse::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Verarbeiten des Mauszeiger Bewegungs Events if(id==CHARTEVENT_MOUSE_MOVE) { //---Koordinaten und der Status der linken Maustaste m_x =(int)lparam; m_y =(int)dparam; m_left_button_state =(bool)int(sparam); //--- Speichere den Wert von dem Zähler für die Aufrufe m_call_counter=::GetTickCount(); //--- Abfrage der Position des Mauszeigers if(!::ChartXYToTimePrice(0,m_x,m_y,m_subwin,m_time,m_level)) return; //--- Abfrage der relativen y-Koordinate if(m_subwin>0) m_y=m_y-m_chart.SubwindowY(m_subwin); } }
Der Timer der Bibliotheks-Engine (die CWndEvents Klasse) muss die folgende Bedingungen beinhalten: Wenn sich der Mauszeiger in den letzten 500ms nicht bewegt hat, sollte der Chart nicht neu gezeichnet werden. Zu diesem Zeitpunkt muss die linke Maustaste losgelassen sein, damit wir die Situation, wo ein schnelles Vorspulen für eine Controls aktiv ist, ausschließen.
//+---------------------------------------------------------------- //| Timer | //+---------------------------------------------------------------- void CWndEvents::OnTimerEvent(void) { //--- Verlassen, wenn der Mauszeiger im Ruhezustand ist (Die Differenz zwischen L aufrufen ist größer als 500 Millisekunden) und die linke Maustaste ist nicht gedrückt. if(m_mouse.GapBetweenCalls()>500 && !m_mouse.LeftButtonState()) return; //--- Verlassen, falls das Array leer ist if(CWndContainer::WindowsTotal()<1) return; //--- Überprüfen der Events von allen Elementen des Timers CheckElementsEventsTimer(); //--- Neuzeichnen des Charts m_chart.Redraw(); }
Problem gelöst. Das Deaktivieren des Neuzeichnens, wenn sich der Mauszeiger bewegt, hat keinen Einfluss auf die Qualität für das Bewegen eines Formulars mit Steuerelementen, da ein Wert von 16ms im Timer-Intervall völlig ausreichend für das Neuzeichnen ist. Dieses Problem wurde jetzt auf eine einfache Art und Weise gelöst, aber es gibt auch noch andere Lösungsmöglichkeiten. Die Optimierung des Bibliothekscodes wird weiter in den kommenden Artikeln der Serie besprochen werden, denn es gibt noch andere Methoden und Möglichkeiten, die helfen können den CPU-Verbrauch effizienter zu machen.
Optimierung der Strukturansicht und der Controls des Datei-Navigators
Wir haben auch herausgefunden, dass die Initialisierung eines einer Baumansicht (CTreeView) sehr viel Zeit benötigt, wenn sich eine größere Anzahl von Elementen in dieser Ansicht befindet. Dieses passiert ebenfalls in dem Datei-Navigator (CFileNavigator), welcher auf diesen Listen-Typ zurückgreift. Um dieses Problem zu lösen, ist es notwendig den dritten Parameter bei der Reservierung von Speicherplatz für einen Array anzugeben::ArrayResize().
Auszug aus der ::ArrayResize() Funktions-Referenz:
…
Bei einer häufigen Speicherzuordnung empfiehlt sich der Einsatz des dritten Parameters, der eine Reserve festlegt, der die Anzahl der physischen Speicherzuweisungen reduziert. Alle nachfolgenden Aufrufe von ArrayResize führen nicht zu einer physischen Umverteilung des Speichers. Wir erinnern daran, dass der dritte Parameter nur während der physischen Speicherzuweisung verwendet wird...
…
Zum Vergleich haben wir nachfolgend die Ergebnisse von verschiedenen Tests mit verschiedenen Werten für die Größe der Reservierung von Arrays in einer Baumansicht aufgeführt: Die Anzahl der Dateien für diesen Test erreicht 15 000.
Abbildung 5. Testergebnisse für das Bilden von Arrays mit Werten für zu reservierenden Speicherplatz
Legen Sie die reservierte Größe für Arrays der Strukturansicht auf 10 000 fest. Die entsprechenden Änderungen wurden in den CTreeView und CFileNavigator Klassen vorgenommen.
Neue Icons für Verzeichnisse und Dateien in dem Datei-Navigator
Dem Datei-Navigator wurden neue Icons für Ordner und Dateien hinzugefügt (die CFileNavigator Klasse), welche denen von Windows 10 entsprechen.. Ihr schlankes Design eignet sich besser für grafische Oberflächen, aber ggf. können auch benutzerdefinierte Versionen verwendet werden.
Abbildung 6. Neue Icons für Verzeichnisse und Dateien in dem Datei-Navigator
Diese Bilder sind am Ende des Artikels erhältlich.
Schlussfolgerung
Die Bibliothek für das Erzeugen von grafischen Interfaces sieht zu dem aktuellen Stand der Entwicklung wie folgt aus: (Schematisch).
Abbildung 6. Die Struktur unserer Bibliothek zum aktuellen Stand der Entwicklung
Die nächsten Artikel über das grafische Interface führen die Entwicklung der Easy And Fast Bibliothek fort. Die Bibliothek wird um zusätzliche Controls erweitert, die bei der Entwicklung von MQL Anwendungen nötig sein könnten. Die vorhandenen Steuerelemente werden verbessert und mit neuen Features ergänzt.
Nachfolgend können Sie die vierte Version (build 4) der Easy And Fast Bibliothek herunterladen. Wenn Sie interessiert sind, können Sie zu einer schnelleren Entwicklung dieses Projektes durch Vorschläge und Lösungen in Form von Kommentaren oder privaten Nachrichten beitragen.