Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 9): Ein konzeptioneller Sprung (II)
Einführung
Im vorherigen Teil haben wir ein Basissystem erstellt, das die Verwendung von Vorlagen innerhalb eines schwebenden Fensters ermöglicht. Obwohl wir viele Änderungen vorgenommen haben, ist der Code noch nicht fertig. Dies wurde absichtlich getan, um die Erklärung einfach zu halten, denn während die Verwendung von Vorlagen in schwebenden Fenstern ziemlich einfach ist, ist die Verwendung von Objekten etwas viel Komplizierteres. Machen Sie sich also bereit für eine völlig neue Aufgabe.
Tatsächlich besteht die größte Schwierigkeit im Zusammenhang mit der Verwendung der Objekte, die wir verwenden, um die HANDELSCHART-Oberfläche im schwebenden Fenster zu erstellen, darin, dass MetaTrader 5 nicht wirklich für diesen Zweck vorgesehen ist. Einige Leser mögen sagen, dass wir die Standardbibliothek zum Erstellen des HANDELSCHART-Fensters verwenden könnten. Aber ich mag es, die Dinge zu komplizieren, und ich möchte es jedem ermöglichen, seine eigene Nutzeroberfläche zu erstellen, so wie wir es vor ein paar Artikeln besprochen haben. In diesem Artikel war jedoch alles einfach. Jetzt müssen wir die Einschränkungen in MetaTrader 5 verstehen, um sie zu umgehen.
Planung
Fangen wir mit dem Anfang an. Der folgende Code verhält sich wie erwartet:
#property copyright "Daniel Jose" #property indicator_chart_window #property indicator_plots 0 //+------------------------------------------------------------------+ int OnInit() { long id = ChartID(); string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand(); ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0); ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10); ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10); ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300); ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300); ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false); ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { return rates_total; } //+------------------------------------------------------------------+
Hier gibt es nichts Kompliziertes, da der Effekt genau wie erwartet war. Mit MQL5 können Sie jedoch noch ein wenig weiter gehen, obwohl Schwierigkeiten auftreten können, wenn Sie versuchen, etwas zu tun, das über das hinausgeht, wofür das System ursprünglich entwickelt wurde. Wenn wir also den obigen Code in etwas wie den folgenden ändern, wird es interessant.
int OnInit() { long id = ChartID(), handle; string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand(), sz1 = (string)MathRand(); ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0); ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10); ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10); ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300); ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300); ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false); ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false); handle = ObjectGetInteger(id, sz0, OBJPROP_CHART_ID); ObjectCreate(handle, sz1, OBJ_CHART, 0, 0, 0); ObjectSetInteger(handle, sz1, OBJPROP_XDISTANCE, 50); ObjectSetInteger(handle, sz1, OBJPROP_YDISTANCE, 50); ObjectSetInteger(handle, sz1, OBJPROP_XSIZE, 300); ObjectSetInteger(handle, sz1, OBJPROP_YSIZE, 300); ObjectSetInteger(handle, sz1, OBJPROP_PRICE_SCALE, false); ObjectSetInteger(handle, sz1, OBJPROP_DATE_SCALE, false); ChartRedraw(handle); return INIT_SUCCEEDED; }
Wir haben die hervorgehobenen Zeilen zum Code hinzugefügt. Wenn Sie ihn auf einem Chart ausführen, sieht das Ergebnis so aus:
Was ist passiert? Wir haben ein Chart auf einem Chart platziert. Wir könnten hier jedes Objekt platzieren, da MQL5 dies zulässt, aber dies hat sowohl Vor- als auch Nachteile. Werfen Sie einen Blick auf die nächste Änderung im Code, um den Vorteil dieses Schritts zu verstehen.
int OnInit() { long id = ChartID(), handle; string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand(), sz1 = (string)MathRand(); ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0); ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10); ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10); ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300); ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300); ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false); ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false); ObjectSetInteger(id, sz0, OBJPROP_SELECTABLE, true); ObjectSetInteger(id, sz0, OBJPROP_SELECTED, true); handle = ObjectGetInteger(id, sz0, OBJPROP_CHART_ID); ObjectCreate(handle, sz1, OBJ_CHART, 0, 0, 0); ObjectSetInteger(handle, sz1, OBJPROP_XDISTANCE, 50); ObjectSetInteger(handle, sz1, OBJPROP_YDISTANCE, 50); ObjectSetInteger(handle, sz1, OBJPROP_XSIZE, 300); ObjectSetInteger(handle, sz1, OBJPROP_YSIZE, 300); ObjectSetInteger(handle, sz1, OBJPROP_PRICE_SCALE, false); ObjectSetInteger(handle, sz1, OBJPROP_DATE_SCALE, false); ChartRedraw(handle); return INIT_SUCCEEDED; }
Die Addition der hervorgehobenen Zeilen hat das folgende Ergebnis erzeugt:
Das bedeutet, dass alles, was sich innerhalb des Objekts befindet, innerhalb des Objekts bleibt. Dies ist erforderlich, wenn wir schwebende Fenster verwenden, da dies die Steuerlogik erheblich vereinfacht. Aber nicht alles ist perfekt: MetaTrader 5 wurde ursprünglich nicht dafür entwickelt. Daher entsteht ein Problem, wenn sich ein Objekt in einem anderen befindet – wir können keine Ereignisse an interne Objekte senden. Um dies zu verstehen, implementieren wir noch ein paar Änderungen am Code. Nun, der endgültige Code lautet wie folgt:
#property copyright "Daniel Jose" #property indicator_chart_window #property indicator_plots 0 //+------------------------------------------------------------------+ int OnInit() { long id = ChartID(), handle; string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand(), sz1 = (string)MathRand(); ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0); ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10); ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10); ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300); ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300); ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false); ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false); ObjectSetInteger(id, sz0, OBJPROP_SELECTABLE, true); ObjectSetInteger(id, sz0, OBJPROP_SELECTED, true); handle = ObjectGetInteger(id, sz0, OBJPROP_CHART_ID); ObjectCreate(handle, sz1, OBJ_CHART, 0, 0, 0); ObjectSetInteger(handle, sz1, OBJPROP_XDISTANCE, 50); ObjectSetInteger(handle, sz1, OBJPROP_YDISTANCE, 50); ObjectSetInteger(handle, sz1, OBJPROP_XSIZE, 300); ObjectSetInteger(handle, sz1, OBJPROP_YSIZE, 300); ObjectSetInteger(handle, sz1, OBJPROP_PRICE_SCALE, false); ObjectSetInteger(handle, sz1, OBJPROP_DATE_SCALE, false); ObjectSetInteger(handle, sz1, OBJPROP_SELECTABLE, true); ObjectSetInteger(handle, sz1, OBJPROP_SELECTED, true); ChartRedraw(handle); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { return rates_total; } //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if (id == CHARTEVENT_OBJECT_CLICK) Print(sparam); } //+------------------------------------------------------------------+
Hier ist das Ergebnis des Codes, der auf der Plattform ausgeführt wird:
Beachten Sie, dass beim Klicken auf das innere Objekt tatsächlich ein Klick auf das äußere Objekt erfolgt, und hier wird es kompliziert. Aber der Programmierer strebt immer danach, ein Experte im Lösen von Problemen zu werden: Sie müssen Probleme lösen, um das gewünschte Ergebnis zu erzielen. Mit diesem Wissen werden wir das System so konstruieren, dass es das HANDELSCHART in einem schwebenden Fenster erstellt und sicherstellt, dass es funktionsfähig ist und ein individuelles Erscheinungsbild hat.
Es gibt eine letzte Phase in diesem Planungsprozess. Obwohl dieser Teil für moderne Computer nicht so akut ist, muss er dennoch berücksichtigt werden: Optimierung der Verarbeitungszeit. Das Problem hängt eher mit der Anzahl der Operationen zusammen, die der Prozessor ausführen muss, als mit der Zeit, die zum Verarbeiten der Informationen benötigt wird. Das vorgeschlagene schwebende Fenstersystem enthält vier Objekte, die sich bewegen können sollten, um auf Ihre Aktionen zu reagieren. Dementsprechend unterliegen alle im Ansichtsfenster platzierten Informationen den eigenen Modifikationen des Fensters. Zumindest wird das HANDELSCHART die Anzahl der Objekte erhöhen. Und obwohl es keinen entsprechenden Rechenaufwand gibt, wird der Code unangenehm und scheint schlecht optimiert zu sein. Wir könnten einfach ein Kontrollsystem hinzufügen und das würde das Problem lösen. Aber es gibt einen eleganteren Vorschlag. Obwohl es zeit- und arbeitsaufwändiger erscheint, reduziert es tatsächlich die Anzahl der Objekte, die gewartet und manipuliert werden müssen.
Umsetzung
Zuerst teilen wir die Erstellung des schwebenden Fensters in mehrere Schritte auf, um die Wiederverwendung von Code zu unterstützen. Als Nächstes erstellen wir zwei neue Funktionen in der Objektklasse C_ChartFloating:
//+------------------------------------------------------------------+ bool StageLocal01(string sz0, ENUM_TIMEFRAMES TimeFrame = PERIOD_CURRENT, int Scale = -1) { m_LimitX = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS); m_LimitY = (int)ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS); if (m_MaxCounter >= def_MaxFloating) return false; CreateBarTitle(); CreateCaption(sz0); CreateBtnMaxMin(); CreateRegion(TimeFrame, Scale); m_Win[m_MaxCounter].handle = ObjectGetInteger(Terminal.Get_ID(), m_Win[m_MaxCounter].szRegionChart, OBJPROP_CHART_ID); return true; } //+------------------------------------------------------------------+ void StageLocal02(int x, int y, int w, int h) { y = (y < 0 ? m_MaxCounter * def_SizeBarCaption : y); m_Win[m_MaxCounter].PosX = -1; m_Win[m_MaxCounter].PosY = -1; m_Win[m_MaxCounter].PosX_Minimized = m_Win[m_MaxCounter].PosX_Maximized = x; m_Win[m_MaxCounter].PosY_Minimized = m_Win[m_MaxCounter].PosY_Maximized = y; SetDimension(w, h, true, m_MaxCounter); SetPosition(x, y, m_MaxCounter); ChartRedraw(m_Win[m_MaxCounter].handle); m_MaxCounter++; } //+------------------------------------------------------------------+
Der neue Code, der ein schwebendes Fenster hinzufügt, sieht wie folgt aus:
bool AddIndicator(string sz0, int x = 0, int y = -1, int w = 300, int h = 200, ENUM_TIMEFRAMES TimeFrame = PERIOD_CURRENT, int Scale = -1) { if (!StageLocal01(sz0, TimeFrame, Scale)) return false; ChartApplyTemplate(m_Win[m_MaxCounter].handle, sz0 + ".tpl"); m_Win[m_MaxCounter].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand(); ObjectCreate(m_Win[m_MaxCounter].handle, m_Win[m_MaxCounter].szVLine, OBJ_VLINE, 0, 0, 0); ObjectSetInteger(m_Win[m_MaxCounter].handle, m_Win[m_MaxCounter].szVLine, OBJPROP_COLOR, clrBlack); StageLocal02(x, y, w, h); return true; }
Dies beeinträchtigt das bereits compilierte System nicht, bietet jedoch eine bessere Nutzung. Achten Sie auf die hervorgehobenen Zeilen: Jetzt erstellen wir eine Funktion zur Verwendung unserer IDE. Der Anfang ist unten dargestellt:
bool Add_RAD_IDE(string sz0, int x, int y, int w, int h) { if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false; StageLocal02(x, y, w, h); return true; }
Beachten Sie, dass die hervorgehobenen Zeilen dieselben Zeilen sind, die wir im vorherigen Code verwendet haben. Das heißt, wir verwenden den Code wieder, und nur die Dinge, die wir anpassen müssen, sind unterschiedlich. Jetzt können wir unserem System mitteilen, dass wir die Tools zum Verwalten der IDE haben. Dazu ändern wir die Objektklasse C_TemplateChart. Der folgende Code zeigt genau, was in der Funktion geändert wird, sodass wir uns von nun an auf die Implementierung eines schwebenden Fensters mit der IDE konzentrieren können, da die gesamte erforderliche Unterstützung bereits korrekt funktioniert.
void AddTemplate(void) { // .... Function code.... if (h == 0) { SetBase(m_Params.Param[TEMPLATE], (bIsSymbol ? m_Params.Param[TEMPLATE] : _Symbol), timeframe, i, w); if (!ChartApplyTemplate(m_Info[m_Counter - 1].handle, m_Params.Param[TEMPLATE] + ".tpl")) if (bIsSymbol) ChartApplyTemplate(m_Info[m_Counter - 1].handle, "Default.tpl"); } if (m_Params.Param[TEMPLATE] == def_NameTemplateRAD) { if ((h > 0) && (w > 0)) Add_RAD_IDE(m_Params.Param[TEMPLATE], 0, -1, w, h); else { C_Chart_IDE::Create(GetIdSubWinEA()); m_Info[m_Counter - 1].szVLine = ""; } }else { if ((w > 0) && (h > 0)) AddIndicator(m_Params.Param[TEMPLATE], 0, -1, w, h, timeframe, i); else { m_Info[m_Counter - 1].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand(); ObjectCreate(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJ_VLINE, 0, 0, 0); ObjectSetInteger(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJPROP_COLOR, clrBlack); } } }
Mal sehen, wie der Code konfiguriert wird, um so flexibel wie möglich zu sein. Dadurch wird verhindert, dass sich das System in Frankenstein verwandelt. Denken Sie beim Ändern des Codes immer daran, dass Sie den Code nicht von Grund auf neu schreiben und dasselbe mehrmals überprüfen müssen. Versuchen Sie immer, die Dinge nur einmal zu überprüfen. Dann können Sie die Dinge so viel wie möglich verwenden und erkunden, bevor Sie neue Tests durchführen müssen. So wächst das System mit guten Voraussetzungen, während der Code nachhaltig und über die Zeit erweiterbar bleibt.
Wenn Sie das System jetzt ausführen, wird es etwas auf dem Chart anzeigen. Aber wir brauchen es, um zumindest die Schnittstelle zu zeigen, die wir zuvor geschrieben haben. Daher müssen wir zusätzliche Änderungen am Code vornehmen. Jetzt haben wir folgenden Code:
bool Add_RAD_IDE(string sz0, int x, int y, int w, int h) { if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false; ChartApplyTemplate(m_Win[m_MaxCounter].handle, "\\Files\\Chart Trade\\IDE.tpl"); StageLocal02(x, y, w, h); return true; }
Hier ist das Ergebnis des App-Starts:
Es wäre sehr gut und schön, wenn man auf die Objekte zugreifen könnte, die in der Vorlage stehen (die Vorlage wird in der im obigen Code hervorgehobenen Zeile geladen). Es ist jedoch nicht möglich. Und hier ist das wichtige Detail: Anstatt Objekte wie zuvor betrachtet zu erstellen, erstellen wir nur die Objekte, die manipuliert werden sollen! Dies spart viel Verarbeitungszeit, wenn wir das Fenster verschieben müssen. Wir haben noch ein weiteres Problem, aber lassen Sie uns zuerst das Handhabungsproblem lösen und das System funktionsfähig machen. Eigentlich ist dieser Teil bereits fertig, es müssen nur noch einige Anpassungen vorgenommen werden, damit die Dinge funktionieren.
Beginnen wir damit, Änderungen an der Vererbungssequenz zwischen Klassen vorzunehmen. Wir müssen dies tun, weil wir keine Mehrfachvererbung haben, also wird die neue Struktur so aussehen:
Aber diese Änderung sollte kein Problem sein: Eine Änderung in der Vererbungssequenz wird den Code überhaupt nicht ändern, aber dafür sorgen, dass er fast fertig ist. Die geänderten Teile sind im Code unten hervorgehoben.
bool Add_RAD_IDE(string sz0, int x, int y, int w, int h) { if ((w <= 0) || (h <= 0)) return false; if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false; ChartApplyTemplate(m_Win[m_MaxCounter].handle, "\\Files\\Chart Trade\\IDE.tpl"); StageLocal02(x, y, w, h); return true; } void AddTemplate(void) { // ..... Código .... if (h == 0) { SetBase(m_Params.Param[TEMPLATE], (bIsSymbol ? m_Params.Param[TEMPLATE] : _Symbol), timeframe, i, w); if (!ChartApplyTemplate(m_Info[m_Counter - 1].handle, m_Params.Param[TEMPLATE] + ".tpl")) if (bIsSymbol) ChartApplyTemplate(m_Info[m_Counter - 1].handle, "Default.tpl"); } if (m_Params.Param[TEMPLATE] == def_NameTemplateRAD) { C_Chart_IDE::Create(Add_RAD_IDE(m_Params.Param[TEMPLATE], 0, -1, w, h)); m_Info[m_Counter - 1].szVLine = ""; }else { if ((w > 0) && (h > 0)) AddIndicator(m_Params.Param[TEMPLATE], 0, -1, w, h, timeframe, i); else { m_Info[m_Counter - 1].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand(); ObjectCreate(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJ_VLINE, 0, 0, 0); ObjectSetInteger(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJPROP_COLOR, clrBlack); } } } bool Create(bool bFloat) { m_CountObject = 0; if ((m_fp = FileOpen("Chart Trade\\IDE.tpl", FILE_BIN | FILE_READ)) == INVALID_HANDLE) return false; FileReadInteger(m_fp, SHORT_VALUE); for (m_CountObject = eRESULT; m_CountObject <= eEDIT_STOP; m_CountObject++) m_ArrObject[m_CountObject].szName = ""; m_SubWindow = ((m_IsFloating = bFloat) ? 0 : GetIdSubWinEA()); m_szLine = ""; while (m_szLine != "</chart>") { if (!FileReadLine()) return false; if (m_szLine == "<object>") { if (!FileReadLine()) return false; if (m_szLine == "type") { if (m_szValue == "102") if (!LoopCreating(OBJ_LABEL)) return false; if (m_szValue == "103") if (!LoopCreating(OBJ_BUTTON)) return false; if (m_szValue == "106") if (!LoopCreating(OBJ_BITMAP_LABEL)) return false; if (m_szValue == "107") if (!LoopCreating(OBJ_EDIT)) return false; if (m_szValue == "110") if (!LoopCreating(OBJ_RECTANGLE_LABEL)) return false; } } } FileClose(m_fp); DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, 0, szMsgIDE[eLABEL_SYMBOL]); return true; } bool LoopCreating(ENUM_OBJECT type) { #define macro_SetInteger(A, B) ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, A, B) #define macro_SetString(A, B) ObjectSetString(Terminal.Get_ID(), m_ArrObject[c0].szName, A, B) int c0; bool b0; string sz0 = m_szValue; while (m_szLine != "</object>") if (!FileReadLine()) return false; else { if (m_szLine == "name") { b0 = false; StringToUpper(m_szValue); for(c0 = eRESULT; (c0 <= eEDIT_STOP) && (!(b0 = (m_szValue == szMsgIDE[c0]))); c0++); if (!b0 && m_IsFloating) return true; else c0 = (b0 ? c0 : m_CountObject); m_ArrObject[c0].szName = StringFormat("%s%04s>%s", def_HeaderMSG, sz0, m_szValue); //... The rest of the function... }
Das mag seltsam erscheinen. Aber hier ist nichts kompliziert. Denken Sie daran, dass das System, wenn wir kein schwebendes Fenster verwenden, bereits in der Lage ist, Ereignisse zu verarbeiten, die in unserer IDE ausgeführt werden. Aber wegen dieses schwebenden Fensters müssen wir alles neu aufbauen. Wir müssen dies jedoch nicht von Grund auf neu machen – wir werden den vorhandenen Code ändern, um IDE an der richtigen Stelle hinzuzufügen. Wir müssen nur die Objekte hinzufügen, die Ereignisse empfangen. Diese Änderungen ermöglichen es uns zu wissen, ob wir alle Elemente oder nur einige davon erstellen müssen.
Nach diesen Änderungen haben wir IDE-Informationen auf dem Diagramm. Aber IDE-Objekte verursachen ein echtes Durcheinander, da die Objekte nicht mit dem schwebenden Fenster verknüpft sind. Das muss jetzt behoben werden.
Zuerst müssen wir die Punkte ermitteln, an denen sich das schwebende Fenster auf dem Chart befindet. Es reicht aus, die Punkte des Objekts zu kennen, das das Fenster darstellt. Aber da wir uns an die objektorientierte Programmierung halten, müssen wir einige Änderungen vornehmen, obwohl das Hauptergebnis der unten stehende Code ist.
//+------------------------------------------------------------------+ struct IDE_Struct { int X, Y, Index; bool IsMaximized; }; //+------------------------------------------------------------------+ class C_ChartFloating { // ... Class code ... //+------------------------------------------------------------------+ void SetPosition(const int X, const int Y, const int c0) { // ... Function code ... if (c0 == m_IDEStruct.Index) { m_IDEStruct.X = m_Win[c0].PosX + 3; m_IDEStruct.Y = m_Win[c0].PosY + def_SizeBarCaption + 3; m_IDEStruct.IsMaximized = m_Win[c0].IsMaximized; } } //+------------------------------------------------------------------+ // ... Class code ... //+------------------------------------------------------------------+ inline IDE_Struct GetIDE_Struct(void) const { return m_IDEStruct; } //+------------------------------------------------------------------+ bool Add_RAD_IDE(string sz0, int x, int y, int w, int h) { if ((w <= 0) || (h <= 0)) return false; if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false; ChartApplyTemplate(m_Win[m_MaxCounter].handle, "\\Files\\Chart Trade\\IDE.tpl"); m_IDEStruct.Index = m_MaxCounter; StageLocal02(x, y, w, h); return true; } //+------------------------------------------------------------------+ //... The rest of the class code }
Da sich die Lösung auf ein Fenster konzentriert — unseren HANDELSCHART — macht es keinen Sinn, die Dinge nach diesem Punkt noch komplizierter zu machen. Jetzt können wir Objekte zumindest anfangs korrekt im Fenster positionieren. Dies geschieht im folgenden Code:
void Resize(int x) { for (int c0 = 0; c0 < m_CountObject; c0++) if (m_IsFloating) { ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, GetIDE_Struct().X + m_ArrObject[c0].iPosX); ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_YDISTANCE, GetIDE_Struct().Y + m_ArrObject[c0].iPosY); }else ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, x + m_ArrObject[c0].iPosX); };
Die obige Funktion sorgt für die korrekte anfängliche Positionierung der Objekte, und das Ergebnis ist unten zu sehen:
Neben der korrekten Ausgangspositionierung reagieren Objekte bereits auf Ereignisse. Das System weist jedoch noch Fehler auf, die behoben werden müssen. Hier ist das erste, was wir ändern werden:
Wie Sie sehen können, wurde das Fenster minimiert, aber die Objekte bleiben auf dem Bildschirm. Dies liegt daran, dass sich Objekte nicht innerhalb des Fensterbereichs befinden (ich habe zuvor erklärt, warum). Dies muss behoben werden, bevor wir fortfahren. Dies kann durch eine einfache Codeänderung erfolgen, die unten gezeigt wird:
void Resize(int x) { for (int c0 = 0; c0 < m_CountObject; c0++) if (m_IsFloating) { ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, GetIDE_Struct().X + m_ArrObject[c0].iPosX + (GetIDE_Struct().IsMaximized ? 0 : Terminal.GetWidth())); ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_YDISTANCE, GetIDE_Struct().Y + m_ArrObject[c0].iPosY + (GetIDE_Struct().IsMaximized ? 0 : Terminal.GetHeight())); }else ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, x + m_ArrObject[c0].iPosX); };
Das Ergebnis sieht wie folgt aus:
Lassen Sie uns nun das Problem mit der Bewegung beheben. Es kann mit dem folgenden Code gelöst werden
void DispatchMessage(int id, long lparam, double dparam, string sparam) { static double AccumulatedRoof = 0.0; bool b0; double d0; static int px = -1, py = -1; C_ChartFloating::DispatchMessage(id, lparam, dparam, sparam); if (m_CountObject < eEDIT_STOP) return; switch (id) { case CHARTEVENT_MOUSE_MOVE: if ((GetIDE_Struct().X != px) || (GetIDE_Struct().Y != py)) { px = GetIDE_Struct().X; py = GetIDE_Struct().Y; Resize(-1); } break; //... The rest of the function ...
Das Endergebnis sieht wie folgt aus:
Schlussfolgerung
Sehen Sie, wie großartig Programmieren ist: Wir beginnen mit einem Problem und lösen dann, ohne größere Änderungen am Code, ein Problem nach dem anderen, und am Ende haben wir funktionierenden Code mit der kleinstmöglichen Codemenge.
Wichtig! Wenn Sie seltsame Informationen auf dem Hintergrund von Chart Trade sehen, liegt das an Update 3228, das Objekte mit Farbe in clrNONE undurchsichtig macht. Verwenden Sie die angehängte IDE-Datei, die das Problem behebt.
Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/10363
- 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.