English Русский 中文 Español 日本語 Português
preview
Charts interessanter machen: Hinzufügen eines Hintergrunds

Charts interessanter machen: Hinzufügen eines Hintergrunds

MetaTrader 5Beispiele | 2 Mai 2022, 16:03
299 6
Daniel Jose
Daniel Jose

Einführung

Viele Arbeitsplätze enthalten ein repräsentatives Bild, das etwas über den Benutzer aussagt. Diese Bilder machen die Arbeitsumgebung schöner und inspirierender, da die Menschen immer versuchen, die schönsten Bilder als Bildschirmhintergrund zu verwenden. Aber wenn wir die Handelsplattform öffnen, sehen wir, dass sie irgendwie langweilig ist. Alles, was wir haben, ist die grafische Darstellung von numerischen Daten.

 


Man kann Bilder und Fotos über einen längeren Zeitraum betrachten, ohne zu ermüden, aber einen Chart auch nur für ein paar Minuten zu betrachten, kann sehr ermüdend sein. Deshalb sollten wir es so einrichten, dass wir den Chart beobachten und analysieren können, während das Bild im Hintergrund uns motiviert und an etwas Gutes erinnert.


Planung

Zunächst einmal müssen wir festlegen, wie das gesamte Projekt ablaufen soll: Wollen wir den Hintergrund des Diagramms von Zeit zu Zeit ändern oder ein einziges Bild für die gesamte Lebensdauer des Programms verwenden, wobei für alle Charts dasselbe Bild verwendet wird? Ich möchte verschiedene Bilder auf verschiedenen Charts platzieren. Zum Beispiel etwas, das die Art des Vermögenswerts repräsentiert, mit dem ich handele, oder etwas, das andeutet, worauf ich bei diesem Vermögenswert achten sollte. Daher wird die resultierende kompilierte Datei kein internes Bild enthalten, sodass wir später jedes gewünschte Bild auswählen können.

Nun gibt es noch eine weitere Sache zu definieren: Wo sollen unsere Bilder gespeichert werden? MetaTrader 5 verfügt über eine Verzeichnisstruktur, die wir für den Zugriff auf Dinge verwenden sollten. Der Verzeichnisbaum kann nicht außerhalb dieses Rahmens verwendet werden. Wenn wir später auf Bilder zugreifen wollen, ist es wichtig zu wissen, wie diese Struktur zu verwenden ist. Da wir planen, den Speicher zu organisieren und im Laufe der Zeit zu pflegen, werden wir einen Ordner im Verzeichnis FILES erstellen und ihn WALLPAPERS nennen. So können wir auf die Bilder zugreifen, ohne den Baum zu verlassen, dessen Wurzel das MQL5-Verzeichnis ist.

Aber warum legen wir die Dateien nicht in den Ordner IMAGES? In diesem Fall müssten wir durch den Baum navigieren, was eine unnötige Aufgabe wäre, die die Logik des Programms verkompliziert. Da wir aber nach größtmöglicher Einfachheit streben, werden wir das nutzen, was MetaTrader 5 uns bietet. Die Struktur wird also wie folgt aussehen:




Danach fügen wir die Bilder wie unten gezeigt ein, d.h. wir trennen die Logobilder von den allgemeinen Hintergrundbildern. Diese Organisation sorgt für Ordnung, da viele verschiedene Bilder als Logos verwendet werden können, wenn wir mit mehreren Assets arbeiten.



Dies ist eine einfache und effiziente Lösung: Fügen Sie so viele Bilder hinzu, wie Sie benötigen, ohne den Betrieb des Programms zu beeinträchtigen. Bitte beachten Sie jetzt ein wichtiges Detail. Die Bilder sind im BITMAP-Format. Sie sollten im 24- oder 32-Bit-Format vorliegen, da diese Formate leicht zu lesen sind: MetaTrader 5 kann diese Formate standardmäßig lesen, also habe ich es so belassen. Natürlich ist es auch möglich, andere Formate zu verwenden, wenn Sie die Leseroutine so programmieren können, dass Sie am Ende ein BITMAP-Bild erhalten. Ich glaube jedoch, dass es einfacher ist, ein Bild mit einem Bildbearbeitungsprogramm in den 24-Bit- oder 32-Bit-Standard zu konvertieren, als eine separate Lesefunktion zu erstellen. Für Dateien im LOGOS-Ordner gelten die gleichen Prinzipien, allerdings mit einigen Ausnahmen, die wir gleich sehen werden.

Nachdem wir nun die Regeln definiert haben, können wir mit der Kodierung beginnen. Der Code folgt den Prinzipien der objektorientierten Programmierung (OOP), sodass Sie ihn leicht in ein Skript oder einen Indikator portieren oder sogar isolieren können, wenn Sie dies wünschen.


Schritt für Schritt

Der Code beginnt mit einigen Definitionen:

//+------------------------------------------------------------------+
enum eType {IMAGEM, LOGO, COR};
//+------------------------------------------------------------------+
input char     user01 = 30;               //Transparency ( 0 a 100 )
input string   user02 = "WallPaper_01";   //File name
input eType    user03 = IMAGEM;           //Chart background type
//+------------------------------------------------------------------+



Hier geben wir an, was wir tun werden. Die Enumeration eType gibt an, was die Hintergrundgrafik sein wird: IMAGE, LOGO oder Farbe. Der Eintrag USER02 gibt den Namen der Datei an, die als Hintergrund verwendet werden soll, sofern in USER03 der Typ IMAGE ausgewählt wurde. USER01 gibt den Grad der Transparenz des Hintergrundbildes an, da es in manchen Fällen die Datenvisualisierung im Chart stören kann. Daher verwenden wir die Transparenz, um diesen Effekt zu minimieren. Der Transparenzwert kann zwischen 0% und 100% liegen: je höher der Wert, desto transparenter das Bild.




Die folgenden Funktionen sollten zu Ihrem Programm hinzugefügt werden:

Funktion Parameter Wo eine Funktion deklariert werden soll  Ergebnis
Init(string szName, char cView) Dateiname und gewünschte Transparenzstufe Als erste Funktion im OnInit-Code Lädt die angegebene BITMAP-Datei und rendert sie mit der angegebenen Transparenz
Init(string szName)  Nur die Datei wird benötigt Als erste Funktion im OnInit-Code Lädt die angegebene BITMAP-Datei ohne jegliche Transparenz
Resize(void) Keine Parameter erforderlich Im Programmcode von OnChartEvent; im Ereignis CHARTEVENT_CHART_CHANGE Angemessene Größenänderung des Bildes im Chart

Sehen wir uns nun an, wie diese Funktionen im Hauptcode verwendet werden können, beginnend mit der unten gezeigten Klasseninitialisierung. Beachten Sie, dass der Nutzer in diesem Fall den Grad der Transparenz angeben kann. Damit der Wert korrekt ist, sollten wir ihn von 100 subtrahieren.

int OnInit()
  {
   if(user03 != COR)
      WallPaper.Init(user03 == IMAGE ? "WallPapers\\" + user02 : "WallPapers\\Logos\\" + _Symbol, (char)(100 - user01));
   return INIT_SUCCEEDED;
  }



Beachten Sie, dass das Bild nicht erscheint, wenn wir den Modus FARBE verwenden. Achten Sie aber auf den ternären Operator ?. Wenn wir ein Bild auswählen, verweist das Programm auf das Verzeichnis WALLPAPER im FILES-Baum. Wenn es sich um LOGO handelt, verweist es ebenfalls auf den entsprechenden Speicherort, aber beachten Sie, dass der Dateiname für das Logo mit dem Symbolnamen übereinstimmen muss, sonst wird ein Fehler erzeugt. Dies alles gilt für fortlaufende Serien. Wenn Sie jedoch einen Vermögenswert mit einem Verfallsdatum verwenden, müssen Sie eine kleine Funktion hinzufügen, um den Teil des Namens zu trennen, der die aktuelle Serie von der abgelaufenen Serie unterscheidet. Das Problem lässt sich lösen, indem man das Bild einfach umbenennt, sodass es den aktuellen Namen wiedergibt. Für diejenigen, die Cross-Orders verwenden, kann es interessant sein, eine separate Routine zur Anpassung des Symbolnamens einzurichten.

Die nächste Funktion, auf die Sie achten sollten, ist die folgende:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   if(id == CHARTEVENT_CHART_CHANGE)
      WallPaper.Resize();
  }



Alle Codes sind sehr kurz, da ich es nicht mag, Dinge zu verkomplizieren. Das erschwert nur, das System zu verbessern oder zu ändern. Versuchen Sie außerdem, es zu Ihrer Regel zu machen. Die obige Funktion garantiert, dass jede Änderung der Chart-Größe die Funktion aufruft, die die Größe des Bildes anpasst, um immer ein schönes Aussehen zu gewährleisten und das Bild vollständig zu rendern.

Unser Klassencode hat die folgenden Eigenschaften:

Funktion Parameter   Ergebnis 
MsgError(const eErr err, int fp) Fehlertyp und Dateideskriptor Schließt die Datei und zeigt die entsprechende Fehlermeldung an.
MsgError(const eErr err) Fehlertyp Zeigt die entsprechende Fehlermeldung an.
LoadBitmap(const string szFileName, uint &data[], int &width, int &height) Dateiname und Daten-Pointer Lädt die gewünschte Datei und gibt ihre Daten im Format data[] sowie ihre Abmessungen in Pixeln zurück.
~C_WallPaper() Keine Parameter erforderlich Ermöglicht das Schließen der Objektklasse.
Init(const string szName, const char cView) Dateiname und Transparenzstufe Korrektes Initialisieren der gesamten Klasse.
Init(const string szName) Dateiname Korrektes Initialisieren der gesamten Klasse.
Destroy(void) Keine Parameter erforderlich Beendet die Klasse auf korrekte Weise.
Resize(void)  Keine Parameter erforderlich Korrektes Ändern der Bildgröße.

Um ein völliges Chaos im Code zu vermeiden, habe ich die Fehlerverarbeitung in einer Funktion konzentriert, die unten gezeigt wird. Das Einzige, was sie tut, ist, eine Nachricht an den Nutzer zu senden, wenn etwas schief läuft. Das macht die Sache einfacher, denn im Falle einer Übersetzung in eine andere Sprache muss man nur die Meldungen in einer einzigen Routine ändern und nicht versuchen, jede verwendete Meldung zu finden.

   bool              MsgError(const eErr err, int fp = 0)
     {
      string sz0;
      switch(err)
        {
         case FILE_NOT_FOUND  :
            sz0 = "File not found";
            break;
         case FAILED_READ     :
            sz0 = "Reading error";
            break;
         case FAILED_ALLOC    :
            sz0 = "Memory error";
            break;
         case FAILED_CREATE   :
            sz0 = "Error creating an internal resource";
            break;
        };
      MessageBox(sz0, "WARNING", MB_OK);
      if(fp > 0)
         FileClose(fp);
      return false;
     }



Die folgende Funktion liest die Datei und lädt sie in den Speicher. Die einzige Information, die wir brauchen, ist der Dateiname, während die Funktion den Rest der Daten ausfüllt. Am Ende erhalten wir die Abmessungen des Bildes und das Bild selbst, allerdings im BITMAP-Format. Es ist wichtig, dies zu beachten, denn obwohl es verschiedene Formate gibt, endet das Ergebnis immer mit BITMAP; nur die Art der Komprimierung unterscheidet das eine Format vom anderen.

   bool              LoadBitmap(const string szFileName, uint &data[], int &width, int &height)
     {
      struct BitmapHeader
        {
         ushort      type;
         uint        size;
         uint        reserv;
         uint        offbits;
         uint        imgSSize;
         uint        imgWidth;
         uint        imgHeight;
         ushort      imgPlanes;
         ushort      imgBitCount;
         uint        imgCompression;
         uint        imgSizeImage;
         uint        imgXPelsPerMeter;
         uint        imgYPelsPerMeter;
         uint        imgClrUsed;
         uint        imgClrImportant;
        } Header;
      int fp;
      bool noAlpha, noFlip;
      uint imgSize;

      if((fp = FileOpen(szFileName + ".bmp", FILE_READ | FILE_BIN)) == INVALID_HANDLE)
         return MsgError(FILE_NOT_FOUND);
      if(FileReadStruct(fp, Header) != sizeof(Header))
         return MsgError(FAILED_READ, fp);
      width = (int)Header.imgWidth;
      height = (int)Header.imgHeight;
      if(noFlip = (height < 0))
         height = -height;
      if(Header.imgBitCount == 32)
        {
         uint tmp[];
         noAlpha = true;
         imgSize = FileReadArray(fp, data);
         if(!noFlip)
            for(int c0 = 0; c0 < height / 2; c0++)
              {
               ArrayCopy(tmp, data, 0, width * c0, width);
               ArrayCopy(data, data, width * c0, width * (height - c0 - 1), width);
               ArrayCopy(data, tmp, width * (height - c0 - 1), 0, width);
              }
         for(uint c0 = 0; (c0 < imgSize && noAlpha); c0++)
            if(uchar(data[c0] >> 24) != 0)
               noAlpha = false;
         if(noAlpha)
            for(uint c0 = 0; c0 < imgSize; c0++)
               data[c0] |= 0xFF000000;
        }
      else
        {
         int byteWidth;
         uchar tmp[];
         byteWidth = width * 3;
         byteWidth = (byteWidth + 3) & ~3;
         if(ArrayResize(data, width * height) != -1)
            for(int c0 = 0; c0 < height; c0++)
              {
               if(FileReadArray(fp, tmp, 0, byteWidth) != byteWidth)
                  return MsgError(FAILED_READ, fp);
               else
                  for(int j = 0, k = 0, p = width * (height - c0 - 1); j < width; j++, k+=3, p++)
                     data[p] = 0xFF000000 | (tmp[k+2] << 16) | (tmp[k + 1] << 8) | tmp[k];
              }
        }
      FileClose(fp);
      return true;
     }



Sehen Sie sich die folgende Codezeile an:

      if((fp = FileOpen(szFileName + ".bmp", FILE_READ | FILE_BIN)) == INVALID_HANDLE)



Bitte beachten Sie, dass die Dateierweiterung an dieser Stelle angegeben wird, d.h. wir geben sie nicht in dem Moment an, in dem wir angeben, was das Bild sein wird, denn wenn wir die Erweiterung angeben, erhalten wir einen "Datei nicht gefunden"-Fehler. Der Rest der Funktion ist recht einfach: zuerst liest sie den Header der Datei und prüft, ob es sich um eine 32-Bit- oder 24-Bit-BITMAP handelt; dann liest sie das Bild entsprechend, da 32-Bit-Bilder eine etwas andere interne Struktur haben als 24-Bit-Bilder.

Die nächste Funktion initialisiert alle Daten für unser Bitmap-Bild, das auf dem Bildschirm angezeigt werden soll. Bitte beachten Sie, dass wir während dieser Funktion die Bitmap-Datei in eine Programmressource umwandeln. Das ist notwendig, weil wir diese Ressource später mit einem Objekt verknüpfen werden, und genau dieses Objekt wird auf dem Bildschirm nicht als Objekt, sondern als Ressource angezeigt. Es scheint schwer zu verstehen, warum wir das so machen. Aber dieser Schritt ermöglicht es uns, mehrere Ressourcen desselben Typs zu erstellen und sie dann mit einem einzigen Objekt zu verknüpfen, das zur Anzeige von etwas verwendet wird. Wenn wir dem Programm eine einzige bestimmte Ressource hinzufügen würden, würden wir sie als interne Ressource definieren und die Datei kompilieren. In diesem Fall wäre es unmöglich, die Ressource zu ändern, ohne den Quellcode neu zu kompilieren. Durch die Erstellung einer dynamischen Ressource ist es jedoch möglich, festzulegen, welche Ressource verwendet werden soll.

   bool              Init(const string szName, const char cView = 100, const int iSub = 0)
     {
      double dValue = ((cView > 100 ? 100 : (cView < 0 ? 0 : cView)) * 2.55) / 255.0;
      m_Id = ChartID();
      if(!LoadBitmap(szName, m_BMP, m_MemWidthBMP, m_MemHeightBMP))
         return false;
      Destroy();
      m_Height = m_MemHeightBMP;
      m_Width = m_MemWidthBMP;
      if(ArrayResize(m_Pixels, (m_MemSizeArr = m_Height * m_Width)) < 0)
         return MsgError(FAILED_ALLOC);
      m_szRcName = "::" + szName + (string)(GetTickCount64() + MathRand());
      if(!ResourceCreate(m_szRcName, m_Pixels, m_Width, m_Height, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE))
         return MsgError(FAILED_CREATE);
      if(!ObjectCreate(m_Id, (m_szObjName = szName), OBJ_BITMAP_LABEL, iSub, 0, 0))
         return MsgError(FAILED_CREATE);
      ObjectSetInteger(m_Id, m_szObjName, OBJPROP_XDISTANCE, 0);
      ObjectSetInteger(m_Id, m_szObjName, OBJPROP_YDISTANCE, 0);
      ObjectSetString(m_Id, m_szObjName, OBJPROP_BMPFILE, m_szRcName);
      ObjectSetInteger(m_Id, m_szObjName, OBJPROP_BACK, true);
      for(uint i = 0; i < m_MemSizeArr; i++)
         m_BMP[i] = (uchar(double(m_BMP[i] >> 24) * dValue) << 24) | m_BMP[i] & 0x00FFFFFF;
      return true;
     }



Das ist alles sehr schön und scheinbar praktisch, aber das Objekt selbst ist nicht in der Lage, die Ressource zu verändern. Das bedeutet, dass es nicht möglich ist, die Art und Weise zu ändern, wie eine Ressource funktioniert oder dargestellt wird, indem man sie einfach mit einem Objekt verknüpft. Das macht die Sache manchmal etwas komplizierter, denn meistens müssen wir die Art und Weise, wie die Ressource geändert werden soll, innerhalb des Objekts kodieren.

Bis zu diesem Punkt konnte ein Bild mit folgendem Code gerendert werden:

      if(ResourceCreate(m_szRcName, m_Pixels, m_Width, m_Height, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE))
         ChartRedraw();



Die Verwendung dieses Codes garantiert jedoch nicht, dass das Bild wie erwartet dargestellt wird, abgesehen von der Tatsache, dass es die genauen Abmessungen des Charts hat. Ich empfehle Ihnen, hochauflösende Bilder zu verwenden. Große Bilder werden besser dargestellt, was die Berechnung erleichtert und Zeit bei der Verarbeitung spart, was in bestimmten Szenarien entscheidend sein kann. Aber selbst dann haben wir immer noch das Problem, dass das Bild nicht korrekt dargestellt wird, da das Objekt die Ressource nicht so verändert, dass sie den Spezifikationen des Objekts entspricht. Wir müssen also etwas tun, um eine korrekte Modellierung der Ressource zu ermöglichen, damit sie mithilfe des Objekts dargestellt werden kann. Die Mathematik im Zusammenhang mit dem Bild reicht von den einfachsten Berechnungen bis hin zu sehr komplizierten Dingen. Da die Verarbeitungszeit für uns aber genauso wichtig ist wie beim Preis-Chart, können wir es uns nicht leisten, übermäßige Berechnungen durchzuführen. Die Dinge sollten so einfach und schnell wie möglich sein. Aus diesem Grund können wir Bilder verwenden, die größer sind als der Chart, da die einzige erforderliche Berechnung die Verkleinerung der Größe ist. Schauen wir uns an, wie das funktionieren wird.

Die mathematischen Beziehungen, die im obigen Chart verwendet werden, können wie folgt ermittelt werden:


Beachten Sie, dass hier f(x) = f(y) gilt, wodurch das Bildverhältnis erhalten bleibt. Dies wird auch als "Seitenverhältnis" bezeichnet, was bedeutet, dass sich das Bild vollständig verändert. Was aber, wenn f(y) nicht von f(x) abhinge, was würde dann mit unserem Bild passieren? Nun, es würde sich überproportional verändern, also jede beliebige Form annehmen. Obwohl wir bei der Verkleinerung kein Problem haben, gilt dies nicht für die Vergrößerung: Wenn f(x) > 1,0 oder f(y) > 1,0 ist, erhalten wir einen Bildzoom, und hier beginnen die Probleme. Das erste Problem tritt in der folgenden Abbildung auf:


Dies geschieht aufgrund des Effekts, der in der folgenden Abbildung zu sehen ist. Beachten Sie, dass die weißen Flächen leere Bereiche darstellen, die im Bild erscheinen, wenn es durch den Zoom-Effekt wächst. Dies geschieht immer, wenn f(x) oder f(y) größer als eins ist, d. h. wenn wir dem roten Pfeil folgen. In der folgenden Abbildung ist f(x) = f(y) = 2,0, d. h. wir vergrößern das Bild um den Faktor 2.


Es gibt mehrere Möglichkeiten, dieses Problem zu lösen, eine davon ist die Interpolation, die stattfinden sollte, wenn ein leerer Block gefunden wird. In diesem Moment sollten wir eine Faktorisierung durchführen und die Zwischenfarbe zwischen der verwendeten Farbe berechnen, um einen Glättungseffekt zu erzielen und leere Punkte aufzufüllen. Es gibt jedoch ein Problem, das mit den Berechnungen zusammenhängt. Selbst wenn die Interpolation schnell durchgeführt wird, ist dies keine geeignete Lösung für MetaTrader 5 Charts mit Echtzeitdaten. Selbst wenn die Größenanpassung ein paar Mal während der gesamten Zeit, in der der Chart auf dem Bildschirm angezeigt wird, durchgeführt wird (da in den meisten Fällen die Größe des Charts kleiner als das Bild ist, in diesem Fall sind f(x) und f(y) gleich oder kleiner als 1,0, und die Interpolation hat keinen Effekt), aber wenn Sie an ein Bild mit einer Größe von 1920 x 1080 (FULL HD) auf demselben Bildschirm denken, erhöht die Interpolation die Verarbeitungszeit erheblich, ohne dass das Endergebnis davon profitiert.

Im Folgenden sehen Sie, wie die Interpolationsberechnung bei einem Bild mit doppelter Größe durchgeführt wird. Natürlich wird es sehr schnell sein, aber vergessen Sie nicht, dass dies in einem 32-Bit-Farbschema oder ARGB geschehen muss, wo wir 4 Bytes mit 8 Bits für die Berechnung haben. Der Grafikprozessor verfügt über Funktionen, mit denen diese Berechnungen schnell durchgeführt werden können, aber der Zugriff auf diese Funktionen über OpenCL bringt möglicherweise keinen praktischen Nutzen, da es zu einer Verzögerung bei der Dateneingabe und -ausgabe durch den Grafikprozessor kommt. Aus diesem Grund kann die Geschwindigkeit der von der GPU durchgeführten Berechnungen keinen Vorteil darstellen.


                             


In Anbetracht dessen denke ich, dass eine etwas schlechtere Bildqualität aufgrund des Glättungseffekts keine große Sache ist, da in den meisten Fällen f(x) oder f(y) nicht höher als 2 sein wird, nur wenn ein FULL HD-Bild auf einem 4k-Bildschirm verwendet wird. In diesem Szenario ist die Glättung minimal und kaum zu sehen. Daher ziehe ich es vor, statt Punkte zu interpolieren, einen Punkt zum nächsten zu ziehen, um leere Werte schnell aufzufüllen und so die Rechenkosten auf ein Minimum zu reduzieren. Die Art und Weise, wie dies geschieht, ist unten dargestellt. Da wir die Daten einfach kopieren, können wir alle 32 Bits in einem Schritt verarbeiten, und zwar so schnell, wie es ein Grafikverarbeitungssystem liefern würde.


                   


Hier ist also die Funktion, die eine schnelle Größenänderung des Bildes ermöglicht.

void Resize(void)
{
        m_Height =(uint) ChartGetInteger(m_Id, CHART_HEIGHT_IN_PIXELS);
        m_Width = (uint) ChartGetInteger(m_Id, CHART_WIDTH_IN_PIXELS);
        double fx = (m_Width * 1.0) / m_MemWidthBMP;
        double fy = (m_Height * 1.0) / m_MemHeightBMP;
        uint pyi, pyf, pxi, pxf, tmp;

        ArrayResize(m_Pixels, m_Height * m_Width);
        ArrayInitialize(m_Pixels, 0x00FFFFFF);
        for (uint cy = 0, y = 0; cy < m_MemHeightBMP; cy++, y += m_MemWidthBMP)
        {
                pyf = (uint)(fy * cy) * m_Width;
                tmp = pyi = (uint)(fy * (cy - 1)) * m_Width;
                for (uint x = 0; x < m_MemWidthBMP; x++)
                {
                        pxf = (uint)(fx * x);
                        pxi = (uint)(fx * (x - 1));
                        m_Pixels[pxf + pyf] = m_BMP[x + y];
                        for (pxi++; pxi < pxf; pxi++) m_Pixels[pxi + pyf] = m_BMP[x + y];
                }
                for (pyi += m_Width; pyi < pyf; pyi += m_Width) for (uint x = 0; x < m_Width; x++) m_Pixels[x + pyi] = m_Pixels[x + tmp];
        }
        if (ResourceCreate(m_szRcName, m_Pixels, m_Width, m_Height, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE))
                ChartRedraw();
}   



Die Funktion hat eine verschachtelte Schleife, die innere Schleife führt die Funktion f(x) aus, die äußere Schleife - f(y). Bei der Ausführung von f(x) kann es zu leeren Bereichen kommen - dies wird in der folgenden Zeile behoben:

for (pxi++; pxi < pxf; pxi++) m_Pixels[pxi + pyf] = m_BMP[x + y];



Wenn eine Differenz zwischen den X-Werten auftritt, wird diese in der obigen Zeile durch Kopieren des letzten Wertes des Bildes behoben. Infolgedessen kommt es zu Aliasing, aber der Rechenaufwand ist in diesen Fällen minimal, da dieses Fragment eine interne Schleife enthält, die so kurz wie möglich läuft, falls sie ausgeführt wird (was nicht immer der Fall sein wird). Wenn Sie Daten ohne diesen Aliasing-Effekt interpolieren möchten, ändern Sie einfach diese Zeile, um die oben beschriebenen Berechnungen durchzuführen.

Sobald die gesamte Zeile berechnet wurde, überprüfen Sie f(y), um leere Bereiche zu vermeiden, wenn f(y) größer als 1 ist. Dies geschieht in der folgenden Zeile:

for (pyi += m_Width; pyi < pyf; pyi += m_Width) for (uint x = 0; x < m_Width; x++) m_Pixels[x + pyi] = m_Pixels[x + tmp];

Auch hier kommt es zu einem Aliasing, aber das kann auf die gleiche Weise behoben werden wie die Änderung des Codes in der vorherigen Zeile. Wir fügen die Breite des neuen Bildes hinzu, weil wir eine Zeile kopieren, die bereits von der Schleife bearbeitet wurde, die für die Bearbeitung von f(x) des neuen Bildes zuständig ist. Würde dies mit einem anderen Wert geschehen, würde das Bild auf seltsame Weise verzerrt werden.


Schlussfolgerung

Ich hoffe, dass diese Idee dazu beiträgt, dass Ihre Charts mehr Spaß machen und stundenlang betrachtet werden können, denn wenn das Hintergrundbild ermüdend wird, können Sie einfach ein anderes wählen, ohne etwas neu kompilieren zu müssen. Wählen Sie einfach das neue Bild aus, das als Hintergrund für Ihr Chart angezeigt werden soll.

Ein letztes Detail sei hier noch erwähnt: Wenn Sie die Klasse, die das Hintergrundbild platziert, in einem EA verwenden möchten, muss sie als Erstes in der INIT-Routine deklariert werden. Dadurch wird verhindert, dass das Hintergrundbild andere vom EA erstellte grafische Objekte überlagert.

Genießen Sie das Endergebnis in vollen Zügen, denn jetzt werden Sie noch tiefer in die Chart-Analyse eintauchen...



Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/10215

Beigefügte Dateien |
EA_With_Wallpaper.zip (7778.85 KB)
Letzte Kommentare | Zur Diskussion im Händlerforum (6)
Rodrigo De Souza Bonatto
Rodrigo De Souza Bonatto | 2 Feb. 2022 in 21:59
Sehr gut
[Gelöscht] | 27 Apr. 2022 in 20:45
Dienstprogramm funktioniert perfekt, aber einige Indikator mit OnChartEvent funktioniert nicht mehr und sind unklickbar, sobald ich diese EA für Hintergrund anwenden
Daniel Jose
Daniel Jose | 2 Mai 2022 in 14:32
Arpit Tailang # : Das Dienstprogramm funktioniert perfekt, abgesehen von einigen sinalisers, dass OnChartEvent verwendet, um zu arbeiten und deinstalliert werden, wenn ich diese EA im Hintergrund
anwenden

De hecho, aconteceu algo envolvendo as atualizações do MT5, que transformam o Wallpaper em algo diferente do esperado e mostrado no artigo, já que o correto que ele seja exibido no FUNDO GRÁFICO, pois na classe C_WallPaper você encontrará a seguinte linha:

ObjectSetInteger ( Terminal.Get_ID(), szName, OBJPROP_BACK , true ); 

Das bedeutet, dass das Objekt nicht mehr im Grundzustand ist, sondern an den Rand gedrängt wird, da das Objekt, das den Bildschirm empfängt, oder die Bitmap, die die Cliquen empfängt, eine Lösung wäre, den Status aller Objekte zu erhöhen oder den Status der Bitmap zu verringern, was in diesem Fall einfacher ist, da sich der Wert des Objekts, das den Bildschirmhintergrund empfängt, mit dem Wert des Objekts OBJPROP_ZORDER ändert, Tentei as duas soluções, mas não consegui stabilizar a coisa toda de forma a corrigir o problema, portanto, e INFELIZMENTE, o papel de parede deve descartado por hora ... Wenn Sie aufpassen, werden Sie sehen, dass das Bitmap-Bild auf dem Körper der Kerzen gezeichnet wird, was anzeigt, dass das Bitmap-Objekt im Vordergrund ist. Auch dies ist nicht das erwartete Verhalten aufgrund der obigen Codezeile, weshalb es alle Klick-Ereignisse empfängt ... 🙁

felipe ramos
felipe ramos | 2 Mai 2022 in 20:40
Die Möglichkeit, die in diesem Dokument enthaltene Funktion zu nutzen
//+------------------------------------------------------------------+
//|                                                   SelectFile.mqh |
//+------------------------------------------------------------------+
#include <Controls\Dialog.mqh>
#include <Controls\Button.mqh>
#include <Controls\Edit.mqh>
#include <Controls\ListView.mqh>
#include <Controls\CheckGroup.mqh>
//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
#define  INDENT_LEFT             (4)             // indent from left
#define  INDENT_TOP              (4)             // indent from top
#define  INDENT_RIGHT            (4)             // indent from right
#define  BUTTON_WIDTH            (60)     // size by X coordinate
#define  BUTTON_HEIGHT   (20)       // size by Y coordinate
#define  EDIT_HEIGHT             (20)       // size by Y coordinate
#define  COMMON_WIDTH            (90)       // size by X coordinate
//+------------------------------------------------------------------+
//| Class CSelectFile                                                |
//+------------------------------------------------------------------+
class CSelectFile : public CDialog
  {
   CEdit             m_filename;
   CButton           m_button_ok;
   CButton           m_button_cancel;
   CListView         m_filelist;
   CCheckGroup       m_common;
   string            m_instance_id,
                     m_files[],
                     m_folders[],
                     m_prevfolder,
                     m_cfolder,
                     m_fullname;
   int               m_numberfiles,
                     m_numberfolders,
                     m_totalfiles,
                     m_fileflag,
                     m_pressbutton;
protected:
   CChart            m_chart;
public:
                     CSelectFile(void);
                    ~CSelectFile(void);
   virtual bool      Create(const long chart,const string name,const int x1,const int y1,const int x2,const int y2);
   int               ChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   string            Filename(){ m_pressbutton=0; return(m_fullname);}
   int               FileFlag(){ return(m_fileflag);}
protected:
   void              OnClickButtonOK(void);
   void              OnClickButtonCancel(void);
   virtual void      OnClickButtonClose(void);
   void              OnChangeList(void);
   void              OnCheckCommon(void);
   void              SetFolder(string m_fol="");
  };
//+------------------------------------------------------------------+
CSelectFile::CSelectFile(void)
  {
   m_instance_id=IntegerToString(rand(),5,'0');
   m_fileflag=0;
   m_pressbutton=0;
   m_fullname="";
   m_cfolder="";
   m_prevfolder="";
   m_numberfiles=0;
   m_numberfolders=0;
  }
//+------------------------------------------------------------------+
CSelectFile::~CSelectFile(void)
  {
   ArrayFree(m_folders);
   ArrayFree(m_files);
   m_chart.Detach();
   CDialog::Destroy();
  }
//+------------------------------------------------------------------+
bool CSelectFile::Create(const long chart,const string name,const int x1,const int y1,const int x2,const int y2)
  {
   if(x2-x1<280 || y2-y1<200) return(false);
   m_chart_id=chart;
   m_name=name;
   m_subwin=0;
//--- initialize chart object
   m_chart.Attach(chart);
//--- specify object and mouse events
   if(!m_chart.EventObjectCreate() || !m_chart.EventObjectDelete() || !m_chart.EventMouseMove())
     {
      Print("CSelectFile: object events specify error");
      m_chart.Detach();
      return(false);
     }
//--- call method of the parent class
   if(!CDialog::Create(m_chart.ChartId(),m_instance_id,m_subwin,x1,y1,x2,y2))
     {
      Print("CSelectFile: expert dialog create error");
      m_chart.Detach();
      return(false);
     }
   Caption(name);
//--- create dependent controls
//--- create list of files
   int _x1=INDENT_LEFT;
   int _y1=INDENT_TOP;
   int _x2=ClientAreaWidth()-INDENT_RIGHT;
   int _y2=_y1+(ClientAreaHeight()-INDENT_TOP*5-EDIT_HEIGHT*2);
   if(!m_filelist.Create(m_chart_id,m_name+"FileList",m_subwin,_x1,_y1,_x2,_y2)) return(false);
   if(!Add(m_filelist)) return(false);
   m_prevfolder="";
   m_cfolder="";
   SetFolder(m_cfolder);
//--- create field of filename
   _x1=INDENT_LEFT;
   _y1=INDENT_TOP+_y2;
   _x2=ClientAreaWidth()-INDENT_RIGHT;
   _y2=_y1+EDIT_HEIGHT+INDENT_TOP;
   if(!m_filename.Create(m_chart_id,m_name+"Filename",m_subwin,_x1,_y1,_x2,_y2)) return(false);
   if(!Add(m_filename)) return(false);
//--- create common check
   _x1=INDENT_LEFT;
   _y1=INDENT_TOP+_y2;
   _x2=_x1+COMMON_WIDTH;
   _y2=_y1+EDIT_HEIGHT;
   if(!m_common.Create(m_chart_id,m_name+"Common",m_subwin,_x1,_y1,_x2,_y2)) return(false);
   if(!Add(m_common)) return(false);
   if(!m_common.AddItem("Common",1)) return(false);
//--- create button Cancel
   _x1=ClientAreaWidth()-INDENT_RIGHT-BUTTON_WIDTH;
   _x2=_x1+BUTTON_WIDTH;
   _y2=_y1+BUTTON_HEIGHT;
   if(!m_button_cancel.Create(m_chart_id,m_name+"Cancel",m_subwin,_x1,_y1,_x2,_y2)) return(false);
   if(!m_button_cancel.Text("Cancel")) return(false);
   if(!Add(m_button_cancel)) return(false);
//--- create button OK
   _x1=_x1-INDENT_RIGHT-BUTTON_WIDTH;
   _x2=_x1+BUTTON_WIDTH;
   if(!m_button_ok.Create(m_chart_id,m_name+"OK",m_subwin,_x1,_y1,_x2,_y2)) return(false);
   if(!m_button_ok.Text("OK")) return(false);
   if(!Add(m_button_ok)) return(false);
//----
   m_pressbutton=0;
   m_fullname="";
   m_chart.Redraw();
//----
   if(Id(m_subwin*CONTROLS_MAXIMUM_ID)>CONTROLS_MAXIMUM_ID)
     {
      Print("CSelectFile: too many objects");
      return(false);
     }
   return(true);
  }
//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
int CSelectFile::ChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==(ON_CLICK+CHARTEVENT_CUSTOM) && lparam==m_button_ok.Id()) OnClickButtonOK();
   else if(id==(ON_CLICK+CHARTEVENT_CUSTOM) && lparam==m_button_cancel.Id()) OnClickButtonCancel();
   else if(id==(ON_CHANGE+CHARTEVENT_CUSTOM) && lparam==m_filelist.Id()) OnChangeList();
   else if(id==(ON_CHANGE+CHARTEVENT_CUSTOM) && lparam==m_common.Id()) OnCheckCommon();
   else if(!CDialog::OnEvent(id,lparam,dparam,sparam)) return(0);
   m_chart.Redraw();
   return(m_pressbutton);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CSelectFile::OnClickButtonOK(void)
  {
   m_fullname=m_filename.Text();
   StringTrimLeft(m_fullname);
   StringTrimRight(m_fullname);
   if(StringLen(m_fullname)>0)
     {
      m_fullname=m_cfolder+m_fullname;
      m_pressbutton=1;
     }
  }
//+------------------------------------------------------------------+
void CSelectFile::OnClickButtonCancel(void)
  {
   m_pressbutton=-1;
   m_fullname="";
  }
//+------------------------------------------------------------------+
void CSelectFile::OnClickButtonClose(void)
  {
   OnClickButtonCancel();
  }
//+------------------------------------------------------------------+
void CSelectFile::OnChangeList(void)
  {
   int i=(int)m_filelist.Value();
   if(i<0) return;
   else if(i==0)
     {
      string s;
      if(m_cfolder==m_prevfolder || s==m_prevfolder)
        {
         m_cfolder="";
         m_prevfolder=m_cfolder;
         SetFolder(m_cfolder);
        }
      else
        {
         s="\\"+m_prevfolder;
         StringReplace(m_cfolder,s,"\\");
         m_prevfolder=m_cfolder;
         if(m_cfolder=="\\") m_cfolder="";
         SetFolder(m_cfolder+"\\");
        }
      m_filename.Text("");
     }
   else if(i<m_numberfolders)
     {
      m_prevfolder=m_folders[i];
      m_cfolder+=m_prevfolder;
      SetFolder(m_cfolder+"\\");
      m_filename.Text("");
     }
   else m_filename.Text(m_filelist.Select());
  }
//+------------------------------------------------------------------+
void CSelectFile::OnCheckCommon(void)
  {
   m_fileflag=m_common.Value()>0?FILE_COMMON:0;
   if(m_fileflag==0)
     {
      m_prevfolder="";
      m_cfolder="";
      SetFolder(m_cfolder);
      m_filename.Text("");
     }
   else
     {
      m_prevfolder="";
      m_cfolder="";
      SetFolder(m_cfolder);
      m_filename.Text("");
     }
  }
//+------------------------------------------------------------------+
void CSelectFile::SetFolder(string fol="")
  {
   string fl,ff,fld=fol;
   StringReplace(fld,"\\\\","\\");
   int i;
   m_filelist.Select(0);
   m_filelist.ItemsClear();
   ArrayResize(m_folders,1);
   ArrayResize(m_files,1);
   if(fld=="Files\\") fl=""; else fl=fld;
   //---folders
   long  hfind=FileFindFirst(fl+"*",ff,m_fileflag);
   if(hfind==INVALID_HANDLE)
     {//empty folder
      m_numberfiles=0;
      m_numberfolders=1;
      m_folders[0]="Files\\"+fld;
      m_filelist.ItemAdd(m_folders[0]);
      m_totalfiles=0;
     }
   else
     {
      m_numberfiles=0;
      m_numberfolders=0;
      do
        {
         if(StringFind(ff,"\\")>1) m_numberfolders++;
         m_numberfiles++;
        }
      while(FileFindNext(hfind,ff));
      FileFindClose(hfind);
      ArrayResize(m_folders,m_numberfolders+1);
      hfind=FileFindFirst(fl+"*",ff,m_fileflag);
      if(hfind==INVALID_HANDLE) return;
      m_numberfolders=1;
      do
        {
         if(StringFind(ff,"\\")>1){ m_folders[m_numberfolders]=ff; m_numberfolders++;}
        }
      while(FileFindNext(hfind,ff));
      FileFindClose(hfind);
      if(fld=="")
        {
         m_folders[0]="Files\\";
         ff="";
        }
      else
        {
         m_folders[0]=fld;
         ff="Files\\";
        }
      m_filelist.ItemAdd(ff+m_folders[0]);
      int nn=m_numberfolders;
      for(i=1; i<nn; i++) m_filelist.ItemAdd(m_folders[i]);
      //---files
      hfind=FileFindFirst(fl+"*.*",ff,m_fileflag);
      m_numberfiles=0;
      do
        {
         if(StringFind(ff,"\\")<0) m_numberfiles++;
        }
      while(FileFindNext(hfind,ff));
      FileFindClose(hfind);
      if(m_numberfiles>0)
        {
         ArrayResize(m_files,m_numberfiles);
         m_numberfiles=0;
         hfind=FileFindFirst(fl+"*.*",ff,m_fileflag);
         if(hfind!=INVALID_HANDLE)
           {
            do
              {
               if(StringFind(ff,"\\")<0) { m_files[m_numberfiles]=ff; m_numberfiles++;}
              }
            while(FileFindNext(hfind,ff));
            FileFindClose(hfind);
           }
         for(i=0; i<m_numberfiles; i++) m_filelist.ItemAdd(m_files[i]);
        }
      m_totalfiles=m_numberfiles+m_numberfolders+1;
     }
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|                                              Test_SelectFile.mq5 |
//+------------------------------------------------------------------+
#include <class_SelectFile.mqh>
//+------------------------------------------------------------------+
CSelectFile *SelectFile=NULL;
//+------------------------------------------------------------------+
int OnInit()
  {
   SelectFile=new CSelectFile();
   if(!SelectFile.Create(0,"Select a file",20,20,350,300)) return(-1);
   return(0);
  }
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(SelectFile!=NULL) delete SelectFile;
  }
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // event ID  
                  const long& lparam,   // event parameter of the long type
                  const double& dparam, // event parameter of the double type
                  const string& sparam) // event parameter of the string type
  {
   if(SelectFile!=NULL)
     {
      int key=SelectFile.ChartEvent(id,lparam,dparam,sparam);
      if(key>0)
        {//press button OK
         string file=SelectFile.Filename();
         int flag=SelectFile.FileFlag();
         delete SelectFile;
         SelectFile=NULL;
         Print("The selected file - ",flag==FILE_COMMON?"[Common]":"",file);
         int handle=FileOpen(file,flag);
         if(handle>0)
           {
            Print(file," - open");
            FileClose(handle);
           }
         else Print(file," - failed to open");
        }
      else if(key<0)
        {//press button Cancel
         delete SelectFile;
         SelectFile=NULL;
         Print("No file selected");
        }
     }
   if(SelectFile==NULL)
     {
      Print("The program is completed");
      ExpertRemove();
     }
  }
//+------------------------------------------------------------------+
tipo mudar a imem no gráfico ao clicar no arquivo. Ich würde gerne wissen, ob das möglich ist, oder ob es nicht möglich ist und ob ich meine Nerven nicht überstrapazieren muss.
Guilherme Mendonca
Guilherme Mendonca | 3 Mai 2022 in 20:05
Daniel Jose #:

De hecho, etwas geschah mit der MT5-Updates, die das Hintergrundbild in etwas anderes als die erwartete und in dem Artikel gezeigt, da die richtige, dass es in der GRAPHIC BACKGROUND angezeigt werden, weil in der C_WallPaper Klasse finden Sie die folgende Zeile:

Das bedeutet, dass das Objekt im Hintergrund bleiben muss, seltsamerweise kommt es in den Vordergrund, deshalb beginnt das OBJEKT, das die BitMap empfängt, die Klicks zu empfangen, eine Lösung wäre, den Status aller Objekte zu erhöhen, oder zu versuchen, den Bitmap-Status zu senken, in diesem Fall wäre die zweite Lösung einfacher, Dies würde durch Ändern des Wertes der Eigenschaft OBJPROP_ZORDER in dem Objekt, das das Hintergrundbild empfängt, erreicht werden, habe ich versucht, beide Lösungen, aber ich konnte nicht stabilisieren die ganze Sache in einer Weise, um das Problem zu beheben, daher, und INFELIZMENTE, muss das Hintergrundbild für jetzt verworfen werden ... Wenn Sie aufpassen, werden Sie sehen, dass das Bitmap-Bild auf den Kerzenkörper gezeichnet wird, was anzeigt, dass das Bitmap-Objekt im Vordergrund ist. Auch dies ist nicht das erwartete Verhalten aufgrund der obigen Codezeile, da es alle Klick-Ereignisse empfängt ... 🙁


Daniel,

Ist es möglich, wenn Sie einen Timer zu setzen und führen Sie eine Funktion zu deaktivieren und aktivieren Sie die Tapete wieder oder etwas, das "bekräftigen" die Funktion, indem Sie die Tapete in den Hintergrund (zurück)?
Da ich ein GUI-Panel verwende, musste ich mich für diese Lösung entscheiden. Einige grafische Elemente müssen gelöscht und neu erstellt werden, damit sie je nach Objekttyp im Hintergrund oder im Vordergrund stehen.

Was Sie mit gleitenden Durchschnitten machen können Was Sie mit gleitenden Durchschnitten machen können
In diesem Artikel werden mehrere Methoden zur Anwendung des Indikators Gleitender Durchschnitt (MA oder Moving Average) vorgestellt. Jede Methode, die eine Kurvenanalyse beinhaltet, wird von Indikatoren begleitet, die die Idee visualisieren. In den meisten Fällen stammen die hier vorgestellten Ideen von den jeweiligen Autoren. Meine einzige Aufgabe bestand darin, sie zusammenzubringen, damit Sie die wichtigsten Ansätze sehen und hoffentlich vernünftigere Handelsentscheidungen treffen können. MQL5-Kenntnisstand - einfach.
Grafiken in der DoEasy-Bibliothek (Teil 97): Unabhängige Handhabung der Bewegung von Formularobjekten Grafiken in der DoEasy-Bibliothek (Teil 97): Unabhängige Handhabung der Bewegung von Formularobjekten
In diesem Artikel werde ich die Implementierung des unabhängigen Ziehens von beliebigen Formularobjekten mit der Maus betrachten. Außerdem werde ich die Bibliothek um Fehlermeldungen und neue Deal-Eigenschaften ergänzen, die zuvor in das Terminal und MQL5 implementiert wurden.
Lernen Sie, wie Sie ein Handelssystem mit Hilfe der Stochastik entwickeln Lernen Sie, wie Sie ein Handelssystem mit Hilfe der Stochastik entwickeln
In diesem Artikel setzen wir unsere Lernserie fort - dieses Mal werden wir lernen, wie man ein Handelssystem mit Hilfe eines der beliebtesten und nützlichsten Indikatoren, dem Stochastik-Oszillator-Indikator, entwirft, um einen neuen Block in unserem Grundlagenwissen zu bilden.
DirectX-Tutorial (Teil I): Zeichnen des ersten Dreiecks DirectX-Tutorial (Teil I): Zeichnen des ersten Dreiecks
Dies ist ein einführender Artikel über DirectX, der die Besonderheiten der Arbeit mit der API beschreibt. Er soll helfen, die Reihenfolge zu verstehen, in der die Komponenten initialisiert werden. Der Artikel enthält ein Beispiel dafür, wie man ein MQL5-Skript schreibt, das ein Dreieck mit DirectX zeichnet.