English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
Bau einer interaktiven Anwendung zur Anzeige von RSS-Feeds in MetaTrader 5

Bau einer interaktiven Anwendung zur Anzeige von RSS-Feeds in MetaTrader 5

MetaTrader 5Experten | 27 Juni 2016, 12:06
624 0
Francis Dube
Francis Dube

Inhalt


Einleitung

Der Beitrag "RSS Nachrichten-Feeds mit Hilfe von MQL4 lesen" beschrieb ein eher rudimentäres Script, mit dessen Hilfe man RSS-Feeds in der Terminal-Konsole mittels einer einfachen Library anzeigen konnte, die ursprünglich eigentlich zum Parsen von HTML-Dokumenten gebaut worden war.

Als später dann MetaTrader 5 und die MQL5 Programmiersprache verfügbar waren, dachte ich, dass es nun möglich sein müsste, eine interaktive Anwendung zu erzeugen, die RSS-Content einfach besser anzeigen könnte. Dieser Beitrag beschreibt die Erstellung dieser Anwendung mit Hilfe der extensiven MQL5 Standard Library und einiger anderer Tools, die von Mitgliedern der MQL5 Community entwickelt wurden.


1 RSS-Dokumente im Allgemeinen

Bevor wir uns detailliert mit den spezifischen Einzelheiten der Anwendung beschäftigen, bin ich schon der Meinung, dass zunächst ein Überblick über den allgemeinen Aufbau eines RSS-Dokuments angebracht wäre.

Um das zu verstehen, was gleich in diesem Artikel beschrieben wird, muss man sich mit der erweiterbaren Auszeichnungssprache und damit zusammenhängenden Konzepten auskennen. Bitte informieren Sie sich im XML-Tutorial, wenn Sie mit XML-Dokumenten noch nicht so vertraut sind. Hinweis: Der Begriff 'Knoten' bezieht sich in diesem Beitrag auf ein Schlagwort in einem XML-Dokument. Wie im oben erwähnten MQL4-Beitrag erklärt, sind RSS-Dateien einfach nur XML-Dokumente mit einer speziellen Schlagwort-Struktur.

Jedes RSS-Dokument hat einen globalen Behälter - das RSS Schlagwort. Das ist bei allen RSS-Dokumenten gleich. Das Kanal-Schlagwort ist dabei immer ein direkter Nachkomme des RSS-Schlagworts. Es enthält Informationen über die Website, die der Feed beschreibt. Und von jetzt ab können sich RSS-Dokumente hinsichtlich der spezifischen Schlagworte, die sie enthalten, unterscheiden, doch gibt es immer einige Schlagworte, die alle Dokumente enthalten sollten, damit sie als RSS-Dateien erkannt werden können.

Und zwar:

  • Titel - Titel des Kanals. Sollte den Namen der Website enthalten;
  • Link - die URL der Website, die diesen Kanal anbietet;
  • Beschreibung - Zusammenfassung, um was es auf dieser Website geht;
  • Element - mind. ein Element-Schlagwort - für den Content.

Die oben gezeigten Schlagworte sollten alle untergeordnete Knoten des Kanals-Schlagworts sein. Der Element-Knoten ist derjenige, der die Daten in Bezug auf einen bestimmten Content enthält.

Jeder Element-Knoten seinerseits muss auch die folgenden Schlagworte enthalten:

  • Titel - Titel des Contents;
  • Link - die URL des Links zum Content;
  • Beschreibung - Zusammenfassung des Contents;
  • Datum - Datum, an dem der Content veröffentlicht wurde.

Alle RSS-Dokumente enthalten die beschriebenen Schlagworte und folgen alle derselben Struktur.

Unten folgt ein Beispiel eines kompletten RSS-Dokuments.

<?xml version="1.0"?>
<rss version="2.0">
  <channel>
    <title>Xul.fr: Tutorials and Applications of the Web 2.0</title>
    <link>http://www.xul.fr/</link>
    <description>Ajax, JavaScript, XUL, RSS, PHP and all technologies of the Web 2.0. Building a CMS, tutorial and application.</description>
    <pubDate>Wed, 07 Feb 2007 14:20:24 GMT</pubDate>
    <item>
    <title>News on interfaces of the Web in 2010</title>
    <link>http://www.xul.fr/de/2010.php</link>
    <description>Steve Jobs explains why iPad does not support Adobe Flash:&lt;em&gt;At Adobe they are lazy. 
    They have the potential to make  interesting things, but they refuse to do so. 
    Apple does not support Flash because it is too buggy.
     Each time a Mac crashes, most often it is because of Flash. Nobody will use Flash. 
     The world is moving  to &lt;a href="http://www.xul.fr/de/html5/" target="_parent"&gt;HTML 5&lt;/a&gt;</description>
     <pubDate>Sat, 11 Dec 10 09:41:06 +0100</pubDate>
    </item>
    <item>
      <title>Textured Border in CSS</title>
      <link>http://www.xul.fr/de/css/textured-border.php</link>
      <description>   The border attribute of the style sheets can vary in color and width, but it was not expected to give it a texture. However, only a CSS rule is required to add this graphic effect...   The principle is to assign a texture to the whole &lt;em&gt;fieldset&lt;/em&gt; and insert into it another &lt;em&gt;fieldset&lt;/em&gt; (for rounded edges) or a &lt;em&gt;div&lt;/em&gt;, whose background is the same as that of the page</description>
      <pubDate>Wed, 29 Jul 09 15:56:54  0200</pubDate>
    </item>
    <item>
      <title>Create an RSS feed from SQL, example with Wordpress</title>
      <link>http://www.xul.fr/feed/rss-sql-wordpress.html</link>
      <description>Articles contain at least the following items: And possibly, author's name, or an image. This produces the following table: The returned value is true if the database is found, false otherwise. It remains to retrieve the data from the array</description>
      <pubDate>Wed, 29 Jul 09 15:56:50  0200</pubDate>
    </item>
    <item>
      <title>Firefox 3.5</title>
      <link>http://www.xul.fr/gecko/firefox35.php</link>
      <description>Les balises audio et vid&#xE9;o sont impl&#xE9;ment&#xE9;es. Le format de donn&#xE9;e JSON est reconnu nativement par Firefox. L'avantage est d'&#xE9;viter l'utilisation de la fonction eval() qui n'est pas s&#xFB;r, ou d'employer des librairies additionnelles, qui est nettement plus lent</description>
      <pubDate>Wed, 24 Jun 09 15:18:47  0200</pubDate>
    </item>
    <item>
      <title>Contestation about HTML 5</title>
      <link>http://www.xul.fr/de/html5/contestation.php</link>
      <description>  Nobody seemed to be worried so far, but the definition of HTML 5 that is intended to be the format of billions of Web pages in coming years, is conducted and decided by a single person! &lt;em&gt;Hey, wait! Pay no attention to the multi-billions dollar Internet corporation behind the curtain. It's me Ian Hickson! I am my own man</description>
      <pubDate>Wed, 24 Jun 09 15:18:29  0200</pubDate>
    </item>
    <item>
      <title>Form Objects in HTML 4</title>
      <link>http://www.xul.fr/javascript/form-objects.php</link>
      <description>   It is created by the HTML &lt;em&gt;form&lt;/em&gt; tag:   The name or id attribute can access by script to its content. It is best to use both attributes with the same identifier, for the sake of compatibility.   The &lt;em&gt;action&lt;/em&gt; attribute indicates the page to which send the form data. If this attribute is empty, the page that contains the form that will be charged the data as parameters</description>
      <pubDate>Wed, 24 Jun 09 15:17:49  0200</pubDate>
    </item>
    <item>
      <title>DOM Tutorial</title>
      <link>http://www.xul.fr/de/dom/</link>
      <description>  The Document Object Model describes the structure of an XML or HTML document, a web page and allows access to each individual element.</description>
      <pubDate>Wed, 06 May 2009 18:30:11 GMT</pubDate>
    </item>    
  </channel>
</rss>


2. Gesamtstruktur der Anwendung

Ich beschreibe jetzt die Information, die der RSS Reader anzeigen soll und geben einen Überblick über die grafische Benutzerschnittstelle der Anwendung.

Der erste Aspekt, den die Anwendung anzeigen sollte, ist der Titel des Kanals, der sich im Titel-Schlagwort befindet. Diese Information dient als Hinweis auf die Website, auf die der Feed verweist.

Die Anwendung sollte zudem auch einen Schnappschuss des gesamten Contents anzeigen, der der Feed beschreibt - also alle Element-Schlagworte im Dokument. Für jedes Element-Schlagwort wird der Titel des Contents angezeigt. Und zum Schluss möchte ich, dass der RSS Reader die Beschreibung des Content anzeigen kann - also die Daten, die im Beschreibungs-Schlagwort jedes Element-Knotens enthalten sind.


2.1 Die Benutzerschnittstelle

Die Benutzerschnittstelle ist eine Funktion der Information, die von der Anwendung angezeigt werden soll.

Meine Vorstellung von einer Benutzerschnittstelle lässt sich am besten durch das Diagramm unten veranschaulichen.


Skizze des Anwendungsdialogs

Abb. 1 Skizze des Anwendungsdialogs


Das Diagramm zeigt die verschiedenen Bereiche, die eine Benutzerschnittstelle ausmachen.

  • Zuerst der Titel-Balken. Dort wird der Kanal-Titel angezeigt;
  • Eingabebereich. Hier geben Benutzer die Internetadresse eines RSS-Feeds ein;
  • Titelbereich. Hier wird der Titel für spezifischen Content angezeigt;
  • Textbereich. Hier wird die Beschreibung des Contents gezeigt;
  • Bereich für Listenansicht. Die scrollbare Liste zeigt die Titel des gesamten Contents an, den der Feed enthält;
  • Die Schaltfläche links stellt Text zurück und leert ihn, der in den Bereichen 'Titel', 'Text' und 'Listenansicht' angezeigt wird;
  • Die Schaltfläche 'aktuellen Feed aktualisieren' holt sich neue Updates für einen aktuell geladenen Feed.

Der RSS Reader wird folgendermaßen funktionieren: wenn das Programm auf ein Chart geladen wird, wird der leere Anwendungsdialog angezeigt. Dort muss der Benutzer dann im Eingabebereich die Internetadresse eines gewünschten RSS-Feeds eingeben und anschließend die Eingabetaste drücken. Jetzt werden alle Content-Titel geladen, d.h. die Schlagwort-Werte für jedes Element-Schlagwort im Bereich Listenansicht. Die Liste ist nummeriert und beginnt bei "1" - dem jüngsten, veröffentlichten Content.

Jedes Element aus der Liste kann angeklickt werden. Sobald es angeklickt wird, wird es hervorgehoben und die entsprechende Beschreibung des Titel-Contents wird im Textbereich angezeigt Gleichzeitig wird der Content-Titel deutlicher im Titelbereich angezeigt. Sollte es während des Ladens des Feeds aus welchem Grund auch immer zu einem Fehler kommen, erscheint im Textbereich eine entsprechende Fehlermeldung.

Mit der Schaltfläche 'Zurücksetzen' kann man dann jeden Text aus dem Textbereich, Listenansicht und dem Titelbereich wieder entfernen.

'Aktuellen Feed aktualisieren' schließlich, sucht einfach nach neuen Updates für den aktuell geladenen Feed.


2.2 Code-Implementierung

Der RSS Reader wird als ein Expert Advisor implementiert und die MQL5 Standard Library wird verwendet.

Der Code wird in einer CRssReader-Klasse aufbewahrt, die ein Nachkomme der CAppDialog-Klasse ist. Die CAppDialog-Klasse, die in der Dialog.mqh 'Include'-Datei steht, ermöglicht die Implementierung des Anwendungsdialogs, der die Funktionalität für einen Titel-Balken sowie Anwendungskontrollen zur Minimierung, Maximierung und Schließen liefert. Dies ist die Grundlage der Benutzerschnittstelle. Auf ihr aufbauend kommen noch andere Bereich hinzu, nämlich: der Titelbereich, Textbereich, Listenansicht-Bereich und Schaltflächen. Jeder Bereich ist eine Kontrolle. Die Schaltflächen werden als eine Schaltflächenkontrolle implementiert, wie in der Button.mqh 'Include'-Datei beschrieben.

Die Listenansicht-Kontrolle, wie in der ListViewArea.mqh 'Include'-Datei definiert, wird zum Bau des Bereichs 'Listenansicht' des RSS Readers verwendet.

Die Bearbeitungs-Kontrolle genügt offensichtlich zum Bau des Eingabebereichs; sie ist in der Edit.mqh Datei definiert.

Bei der Implementierung der Abschnitte 'Titelbereich' und 'Textbereich' stoßen wir jedoch auf eine spezielle Herausforderung, denn beide müssen Text unterstützen können, der in mehreren Zeilen angezeigt werden kann. Textobjekte in MQL5 erkennen keine Zeichen zur Füllung neuer Zeilen. Ein weiteres Problem besteht darin, dass nur eine begrenzte Anzahl Zeichen in einer Zeile eines Textobjekts angezeigt werden kann. Das heißt: Wenn man ein Textobjekt mit einer reichlich langen Beschreibung erzeugt, wird das Objekt angezeigt, der Text jedoch irgendwann abgeschnitten und eben nur eine gewisse Anzahl Zeichen angezeigt. Durch Herumprobieren habe ich herausgefunden, dass die Zeichenbeschränkung bei 63 Zeichen liegt - inkl. Leer- und Satzzeichen.

Um diese Probleme zu lösen, habe ich beschlossen, beide Bereiche als modifizierte Listenansicht-Kontrollen zu implementieren. Für den Abschnitt 'Titelbereich' ist die modifizierte Listenansicht nun nicht mehr scrollbar und hat eine feste Anzahl von Listenelementen (2). Jedes Listenelement ist auch nicht mehr anklickbar oder auswählbar, und die Kontrolle sieht physisch nun tatsächlich nicht mehr wie eine Liste aus. Diese 2 Listenelemente repräsentieren zwei Textzeilen. Sollte der Text zu lang sein, um in eine Zeile zu passen, wird er entsprechend aufgeteilt und als zwei Textzeilen angezeigt. Die Kontrolle für den Abschnitt 'Titelbereich wird in der 'TitleArea.mqh Datei definiert.

Für den Abschnitt 'Textbereich' wird ganz ähnlich verfahren, diesmal allerdings ist die Anzahl der Listenelemente dynamisch und die modifizierte Listenansicht-Kontrolle ist senkrecht scrollbar. Diese Kontrolle findet sich in der TextArea.mqh Datei.

Die bislang angesprochenen Libraries kümmern sich um die Benutzerschnittstelle. Doch es gibt noch eine weitere Library, die für diese Anwendung extrem wichtig ist: nämlich die Library zum Parsen der XML-Dokumente. Und die sehen wir uns jetzt an.


2.3 Der leichte XML-Parser

Da ein RSS-Dokument ja eine XML-Datei ist, wird die, von liquinaut entwickelte EasyXML - XML Parser Library verwendet, die in der CodeBase gefunden werden kann.

Sie ist ziemlich umfangreich und enthält fast die gesamte, für unseren RSS Reader nötige Funktionalität. Ich habe die Original-Library etwas abgewandelt und einige Extra-Features hinzugefügt, die meiner Meinung nach notwendig waren.

Doch das waren nur kleinere Ergänzungen. Zunächst habe ich eine extra Methode hinzugefügt, nämlich die sog. loadXmlFromUrlWebReq() Methode. Sie stellt einfach nur eine Alternative zur Verwendung der loadXmlFromUrl() Methode dar, die zur Verarbeitung von Internet-Anfragen auf die WinInet Library zurückgreift. loadXmlFromUrlWebReq() hingegen, arbeitet mir der WebRequest()Funktion um Downloads aus dem Internet zu ermöglichen.

//+------------------------------------------------------------------+
//| load xml by given url using MQL5 webrequest function             |
//+------------------------------------------------------------------+
bool CEasyXml::loadXmlFromUrlWebReq(string pUrl)
  {
//---
   string cookie=NULL,headers;
   char post[],result[];
   int res;
//---
   string _url=pUrl;
   string sStream;
   res=WebRequest("GET",_url,cookie,NULL,5000,post,0,result,headers);
//--- check error
   if(res==-1)
     {
      Err=EASYXML_ERR_WEBREQUEST_URL;
      return(Error());
     }
//---success downloading file
   sStream=CharArrayToString(result,0,-1,CP_UTF8);
//---set up cach file
   if(blSaveToCache)
     {
      bool bResult=writeStreamToCacheFile(sStream);
      if(!bResult) Error(-1,false);
     }
//---
   return(loadXmlFromString(sStream));
  }

Als nächstes habe ich die GetErrorMsg() Methode hinzugefügt. Damit kann man die Fehlermeldungsausgabe durch den Parser abrufen, sobald ein Fehler auftaucht.

string            GetErrorMsg(void){   return(ErrMsg);}

Diese zweite Ergänzung diente dazu, einen ziemlich ernsthaften Mangel des easyxml Parsers zu bereinigen, der mir beim Testen aufgefallen ist.

Ich habe nämlich festgestellt, dass die Library keine XML-Formatvorlagen-Deklarierungen erkennen konnte. Der Code missinterpretiert eine Formatvorlagen-Deklarierung als Eigenschaft. Und damit bleibt das Programm in einer Endlosschleife hängen, da der Code ständig nach dem entsprechenden Wert der Eigenschaft sucht - den es ja niemals gegeben hat.

Doch das konnte mit etwas Änderung an der skipProlog() Methode schnell bereinigt werden.

//+------------------------------------------------------------------+
//| skip xml prolog                                                  |
//+------------------------------------------------------------------+
bool CEasyXml::skipProlog(string &pText,int &pPos)
  {
//--- skip xml declaration
   if(StringCompare(EASYXML_PROLOG_OPEN,StringSubstr(pText,pPos,StringLen(EASYXML_PROLOG_OPEN)))==0)
     {
      int iClose=StringFind(pText,EASYXML_PROLOG_CLOSE,pPos+StringLen(EASYXML_PROLOG_OPEN));

      if(blDebug) Print("### Prolog ###    ",StringSubstr(pText,pPos,(iClose-pPos)+StringLen(EASYXML_PROLOG_CLOSE)));

      if(iClose>0)
        {
         pPos=iClose+StringLen(EASYXML_PROLOG_CLOSE);
           } else {
         Err=EASYXML_INVALID_PROLOG;
         return(false);
        }
     }
//--- skip stylesheet declarations
   if(StringCompare(EASYXML_STYLESHEET_OPEN,StringSubstr(pText,pPos,StringLen(EASYXML_STYLESHEET_OPEN)))==0)
     {
      int iClose=StringFind(pText,EASYXML_STYLESHEET_CLOSE,pPos+StringLen(EASYXML_STYLESHEET_OPEN));

      if(blDebug) Print("### Prolog ###    ",StringSubstr(pText,pPos,(iClose-pPos)+StringLen(EASYXML_STYLESHEET_CLOSE)));

      if(iClose>0)
        {
         pPos=iClose+StringLen(EASYXML_STYLESHEET_CLOSE);
           } else {
         Err=EASYXML_INVALID_PROLOG;
         return(false);
        }
     }
//--- skip comments
   if(!skipWhitespaceAndComments(pText,pPos,"")) return(false);

//--- skip doctype
   if(StringCompare(EASYXML_DOCTYPE_OPEN,StringSubstr(pText,pPos,StringLen(EASYXML_DOCTYPE_OPEN)))==0)
     {
      int iClose=StringFind(pText,EASYXML_DOCTYPE_CLOSE,pPos+StringLen(EASYXML_DOCTYPE_OPEN));

      if(blDebug) Print("### DOCTYPE ###    ",StringSubstr(pText,pPos,(iClose-pPos)+StringLen(EASYXML_DOCTYPE_CLOSE)));

      if(iClose>0)
        {
         pPos=iClose+StringLen(EASYXML_DOCTYPE_CLOSE);
           } else {
         Err=EASYXML_INVALID_DOCTYPE;
         return(false);
        }
     }

//--- skip comments
   if(!skipWhitespaceAndComments(pText,pPos,"")) return(false);

   return(true);
  }

Abgesehen von diesem Problem bekommt liquinaut mein volles Lob - easyxml.mqh ist ein exzellentes Tool


2.4 Code des Expert Advisors

Jetzt sind alle für die Anwendung erforderlichen Libraries beschrieben, also können wir nun all diese Komponenten zusammenbringen, um die CRssReader-Klasse zu definieren.

Bitte beachten Sie, dass der Code des RSS Reader Expert Advisors mit der Definition der CRssReader-Klasse beginnt.

//+------------------------------------------------------------------+
//|                                                    RssReader.mq5 |
//|                                                          Ufranco |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Ufranco"
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Controls\Dialog.mqh>
#include <Controls\Edit.mqh>
#include <Controls\Button.mqh>
#include <TitleArea.mqh>
#include <TextArea.mqh>
#include <ListViewArea.mqh>
#include <easyxml.mqh>
//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
//--- indents and gaps
#define INDENT_LEFT                         (11)      // indent from left (with allowance for border width)
#define INDENT_TOP                          (11)      // indent from top (with allowance for border width)
#define INDENT_RIGHT                        (11)      // indent from right (with allowance for border width)
#define INDENT_BOTTOM                       (11)      // indent from bottom (with allowance for border width)
#define CONTROLS_GAP_X                      (5)       // gap by X coordinate
#define CONTROLS_GAP_Y                      (5)       // gap by Y coordinate

#define EDIT_HEIGHT                         (20)      // size by Y coordinate
#define BUTTON_WIDTH                        (150)     // size by X coordinate
#define BUTTON_HEIGHT                       (20)      // size by Y coordinate
#define TEXTAREA_HEIGHT                     (131)     // size by Y coordinate
#define LIST_HEIGHT                         (93)      // size by Y coordinate

Wir beginnen alle notwendigen Dateien mit einzuschließen. Die 'Definieren' Direktiven werden zur Einrichtung der physischen Parameter der Kontrollen in Pixel verwendet.

INDENT_LEFT, INDENT_RIGHT, INDENT_TOP und INDENT_DOWN legen den Abstand zwischen einer Kontrolle und der Kante des Anwendungsdialogs fest.

  • CONTROLS_GAP_Y ist der senkrechte Abstand zwischen zwei Kontrollen;
  • EDIT_HEIGHT setzt die Höhe der Bearbeitungs-Kontrolle fest, die den Eingabebereich ausmacht;
  • BUTTON_WIDTH und BUTTON_HEIGHT definieren die Breite und Höhe aller Schaltflächen-Kontrollen;
  • TEXTAREA_HEIGHT ist die Höhe des Abschnitts 'Textbereich';
  • LIST_HEIGHT richtet die Höhe der Listenansicht-Kontrolle ein.

Ist dies alles definiert, beginnen wir mit der Festlegung der CRssReader-Klasse.

//+------------------------------------------------------------------+
//| Class CRssReader                                                 |
//| Usage: main class for the RSS application                        |
//+------------------------------------------------------------------+
class CRssReader : public CAppDialog
  {
private:
   int               m_shift;                    // index of first item tag
   string            m_rssurl;                   // copy of web address of last feed 
   string            m_textareaoutput[];         // array of strings prepared for output to the text area panel
   string            m_titleareaoutput[];        // array of strings prepared for output to title area panel
   CButton           m_button1;                  // the button object
   CButton           m_button2;                  // the button object      
   CEdit             m_edit;                     // input panel
   CTitleArea        m_titleview;                // the display field object
   CListViewArea     m_listview;                 // the list object
   CTextArea         m_textview;                 // text area object
   CEasyXml          m_xmldocument;              // xml document object
   CEasyXmlNode     *RssNode;                    // root node object
   CEasyXmlNode     *ChannelNode;                // channel node object
   CEasyXmlNode     *ChannelChildNodes[];        // array of channel child node objects

Wie bereits angesprochen, erbt CRssReader von der CAppDialog-Klasse.

Die Klasse hat mehrere private Eigenschaften, die hier aufgeführt werden:

  • m_shift - Diese 'ganzzahlige' Variable speichert den Index des ersten Element-Knotens im ChannelChildnodes Array;
  • m_rssurl - ist ein String-Wert, der eine Kopie der letzten eingegeben URL aufbewahrt;
  • m_textareaoutput[] - ist ein Array mit Strings, deren jedes Element einer Textzeile mit einer bestimmten Zeichenanzahl entspricht;
  • m_titleareaoutput[] - ist ebenfalls ein Array mit Strings und erfüllt den gleichen Zweck, wie der vorherige String-Array;
  • m_button1 und m_button2 sind Objekte des CButton Typs;
  • m_listview ist ein Objekt, das eine Listenkontrolle repräsentiert;
  • m_edit property ist ein CEdit Objekt und implementiert den Eingabebereich;
  • m_titleview ist ein Objekt für das Anzeigefeld des Titelbereichs;
  • m_textview - ist das Objekt für den Abschnitt 'Textbereich';
  • m_xmldocument ist ein XML-Dokumentenobjekt;
  • RssNode ist das Stammknoten-Objekt;
  • ChannelNode ist das Objekt des Kanalknotens;
  • Das ChannelChildNodes Array ist ein Set von Zeigern, die auf die direkten Nachkommen des Channel Schlagworts zeigen.

Unser Klasse besitzt nur zwei public exponierte Methoden:

public:
                     CRssReader(void);
                    ~CRssReader(void);
   //--- create
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   //--- chart event handler
   virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);

Die erste - Create() - richtet die Größe und Ausgangspositionen des Anwendungsdialogs ein.

Sie initialisiert zudem auch alle Kontrollen der RSS Reader Anwendung (nicht vergessen: unsere Klasse erbt von der CAppDialog Klasse, sodass deshalb public Methoden der übergeordneten Klasse und seiner Vorgängers von Instanzen des CRssReader aufgerufen werden können).

//+------------------------------------------------------------------+
//| Create                                                           |
//+------------------------------------------------------------------+
bool CRssReader::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2)
  {
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
      return(false);
//--- create dependent controls
   if(!CreateEdit())
      return(false);
   if(!CreateButton1())
      return(false);
   if(!CreateButton2())
      return(false);
   if(!CreateTitleView())
      return(false);
   if(!CreateListView())
      return(false);
   if(!CreateTextView())
      return(false);
//--- succeed
   return(true);
  }

Die zweite Methode - OnEvent() - aktiviert als Funktion die Interaktivität durch Zuweisung spezifischer Ereignisse an eine entsprechend Kontrolle und eine Handler-Funktion.

//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CRssReader)
ON_EVENT(ON_CHANGE,m_listview,OnChangeListView)
ON_EVENT(ON_END_EDIT,m_edit,OnObjectEdit)
ON_EVENT(ON_CLICK,m_button1,OnClickButton1)
ON_EVENT(ON_CLICK,m_button2,OnClickButton2)
EVENT_MAP_END(CAppDialog)


2.5. Methoden zur Initialisierung von Kontrollen

Die geschützten CreateEdit(), CreateButton1() ,CreateButton2(), CreateTitleView(), CreateListView() und CreateTextView() Methoden werden alle von der Create() Hauptfunktion zur Initialisierung einer entsprechenden Kontrolle aufgerufen.

protected:
   // --- creating controls
   bool              CreateEdit(void);
   bool              CreateButton1(void);
   bool              CreateButton2(void);
   bool              CreateTitleView(void);
   bool              CreateListView(void);
   bool              CreateTextView(void);

Und in jeder dieser Funktionen werden die Größe, Position und die Eigenschaften einer Kontrolle eingerichtet (also Schriftgröße, Farbe, Rahmenfarbe,Rahmenart).

//+------------------------------------------------------------------+
//| Create the display field                                         |
//+------------------------------------------------------------------+
bool CRssReader::CreateEdit(void)
  {
//--- coordinates
   int x1=INDENT_LEFT;
   int y1=INDENT_TOP;
   int x2=ClientAreaWidth()-INDENT_RIGHT;
   int y2=y1+EDIT_HEIGHT;
//--- create
   if(!m_edit.Create(m_chart_id,m_name+"Edit",m_subwin,x1,y1,x2,y2))
      return(false);
   if(!m_edit.Text("Please enter the web address of an Rss feed"))
      return(false);
   if(!m_edit.ReadOnly(false))
      return(false);
   if(!Add(m_edit))
      return(false);
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Create button 1                                                  |
//+------------------------------------------------------------------+ 
bool CRssReader::CreateButton1(void)
  {
//--- coordinates
   int x1=INDENT_LEFT;
   int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y);
   int x2=x1+BUTTON_WIDTH;
   int y2=y1+BUTTON_HEIGHT;
//--- create
   if(!m_button1.Create(m_chart_id,m_name+"Button1",m_subwin,x1,y1,x2,y2))
      return(false);
   if(!m_button1.Text("Reset"))
      return(false);
   if(!m_button1.Font("Comic Sans MS"))
      return(false);
   if(!m_button1.FontSize(8))
      return(false);
   if(!m_button1.Color(clrWhite))
      return(false);
   if(!m_button1.ColorBackground(clrBlack))
      return(false);
   if(!m_button1.ColorBorder(clrBlack))
      return(false);
   if(!m_button1.Pressed(true))
      return(false);
   if(!Add(m_button1))
      return(false);
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Create button 2                                                  |
//+------------------------------------------------------------------+ 
bool CRssReader::CreateButton2(void)
  {
//--- coordinates
   int x1=(ClientAreaWidth()-INDENT_RIGHT)-BUTTON_WIDTH;
   int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y);
   int x2=ClientAreaWidth()-INDENT_RIGHT;
   int y2=y1+BUTTON_HEIGHT;
//--- create
   if(!m_button2.Create(m_chart_id,m_name+"Button2",m_subwin,x1,y1,x2,y2))
      return(false);
   if(!m_button2.Text("Update current feed"))
      return(false);
   if(!m_button2.Font("Comic Sans MS"))
      return(false);
   if(!m_button2.FontSize(8))
      return(false);
   if(!m_button2.Color(clrWhite))
      return(false);
   if(!m_button2.ColorBackground(clrBlack))
      return(false);
   if(!m_button2.ColorBorder(clrBlack))
      return(false);
   if(!m_button2.Pressed(true))
      return(false);
   if(!Add(m_button2))
      return(false);
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Create the display field                                         |
//+------------------------------------------------------------------+
bool CRssReader::CreateTitleView(void)
  {
//--- coordinates
   int x1=INDENT_LEFT;
   int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y)+BUTTON_HEIGHT+CONTROLS_GAP_Y;
   int x2=ClientAreaWidth()-INDENT_RIGHT;
   int y2=y1+(EDIT_HEIGHT*2);
   m_titleview.Current();
//--- create 
   if(!m_titleview.Create(m_chart_id,m_name+"TitleView",m_subwin,x1,y1,x2,y2))
     {
      Print("error creating title view");
      return(false);
     }
   else
     {
      for(int i=0;i<2;i++)
        {
         m_titleview.AddItem(" ");
        }
     }
   if(!Add(m_titleview))
     {
      Print("error adding title view");
      return(false);
     }
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Create the "ListView" element                                    |
//+------------------------------------------------------------------+
bool CRssReader::CreateListView(void)
  {

//--- coordinates
   int x1=INDENT_LEFT;
   int y1=INDENT_TOP+((EDIT_HEIGHT+CONTROLS_GAP_Y)*2)+20+TEXTAREA_HEIGHT+CONTROLS_GAP_Y+BUTTON_HEIGHT+CONTROLS_GAP_Y;
   int x2=ClientAreaWidth()-INDENT_RIGHT;
   int y2=y1+LIST_HEIGHT;
//--- create
   if(!m_listview.Create(m_chart_id,m_name+"ListView",m_subwin,x1,y1,x2,y2))
      return(false);
   if(!Add(m_listview))
      return(false);
//--- fill out with strings
   for(int i=0;i<20;i++)
      if(!m_listview.AddItem(" "))
         return(false);
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Create the display field                                         |
//+------------------------------------------------------------------+
bool CRssReader::CreateTextView(void)
  {
//--- coordinates
   int x1=INDENT_LEFT;
   int y1=INDENT_TOP+((EDIT_HEIGHT+CONTROLS_GAP_Y)*2)+20+BUTTON_HEIGHT+CONTROLS_GAP_Y;
   int x2=ClientAreaWidth()-INDENT_RIGHT;
   int y2=y1+TEXTAREA_HEIGHT;
   m_textview.Current();
//--- create 
   if(!m_textview.Create(m_chart_id,m_name+"TextArea",m_subwin,x1,y1,x2,y2))
     {
      Print("error creating text area view");
      return(false);
     }
   else
     {
      for(int i=0;i<1;i++)
        {
         m_textview.AddItem(" ");
        }
      m_textview.VScrolled(true);
      ChartRedraw();
     }
   if(!Add(m_textview))
     {
      Print("error adding text area view");
      return(false);
     }
//----success      
   return(true);
  }


2.6 Methoden zur Verarbeitung von RSS-Dokumenten

// --- rss document processing
   bool              LoadDocument(string filename);
   int               ItemNodesTotal(void);
   void              FreeDocumentTree(void);

2.6.1 LoadDocument()

Diese Funktion muss ein paar wichtige Rollen spielen. Zunächst muss sie Internet-Anfragen verarbeiten. loadXmlFromUrlWebReq() wird aufgerufen, wenn eine RSS-Datei heruntergeladen werden soll.

Ist das dann erfolgreich geschehen, geht die Funktion zu ihrer zweiten Aufgabe über, nämlich der Initialisierung der Zeiger RssNode und ChannelNode und zudem dem Füllen des ChannelChildnodes Arrays. Hier werden die Eigenschaften m_rssurl und m_shift eingerichtet. Ist all dies erledigt, liefert die Funktion 'true'.

Kann eine RSS-Datei nicht heruntergeladen werden, wird aus 'Titelbereich', 'Listenansicht-Bereich' und 'Textbereich' jeder Text geleert und im Titel-Balken erscheint eine Status-Meldung, gefolgt von der Ausgabe einer Fehlermeldung im 'Textbereich'. In so einem Fall liefert die Funktion 'false'.

//+------------------------------------------------------------------+
//|   load document                                                  |
//+------------------------------------------------------------------+
bool CRssReader::LoadDocument(string filename)
  {
   if(!m_xmldocument.loadXmlFromUrlWebReq(filename))
     {
      m_textview.ItemsClear();
      m_listview.ItemsClear();
      m_titleview.ItemsClear();
      CDialog::Caption("Failed to load Feed");
      if(!m_textview.AddItem(m_xmldocument.GetErrorMsg()))
         Print("error displaying error message");
      return(false);
     }
   else
     {
      m_rssurl=filename;
      RssNode=m_xmldocument.getDocumentRoot();
      ChannelNode=RssNode.FirstChild();
      if(CheckPointer(RssNode)==POINTER_INVALID || CheckPointer(ChannelNode)==POINTER_INVALID)
         return(false);
     }
   ArrayResize(ChannelChildNodes,ChannelNode.Children().Total());
   for(int i=0;i<ChannelNode.Children().Total();i++)
     {
      ChannelChildNodes[i]=ChannelNode.Children().At(i);
     }
   m_shift=ChannelNode.Children().Total()-ItemNodesTotal();
   return(true);
  }


2.6.2 ItemNodesTotal()

Diese Hilfsfunktion wird in der LoadDocument() Methode verwendet. Sie liefert einen ganzzahligen Wert, der die Anzahl der Elementknoten ist, die die Nachkommen des Schlagworts sind.

Gibt es keine Elementknoten, ist das Dokument ein ungültiges RSS-Dokumente und die Funktion liefert "0".

//+------------------------------------------------------------------+
//| function counts the number of item tags in document              |
//+------------------------------------------------------------------+
int CRssReader::ItemNodesTotal(void)
  {
   int t=0;
   for(int i=0;i<ChannelNode.Children().Total();i++)
     {
      if(ChannelChildNodes[i].getName()=="item")
        {
         t++;
        }
      else continue;
     }
   return(t);
  }

2.6.3 FreeDocumentTree()

Diese Funktion setzt alle CEasyXmlNode Zeiger zurück.

Zuerst werden die Elemente des ChannelChildnodes Arrays mittels Aufrufs der Shutdown() Methode der CArrayObj Klasse gelöscht. Danach wird das Array mit einem einzigen Aufruf von ArrayFree() freigegeben.

Anschließend wird der Zeiger auf den Kanal-Knoten gelöscht und der Dokumentenbaum des easyxml Parsers geleert. Dadurch werden die RssNode und ChannelNode Zegier zu 'schlechten' Zeiger, und bekommen daher beide den NULL-Wert zugewiesen.

//+------------------------------------------------------------------+
//| free document tree and reset pointer values                      |
//+------------------------------------------------------------------+ 
void CRssReader::FreeDocumentTree(void)
  {
   ChannelNode.Children().Shutdown();
   ArrayFree(ChannelChildNodes);
   RssNode.Children().Shutdown();
   m_xmldocument.Clear();
   m_shift=0;
   RssNode=NULL;
   ChannelNode=NULL;
  }


2.7 Methoden zur Extrahierung von Informationen aus dem Dokumentenbaum

Mit diesen Funktionen erhält man den Text aus einem RSS-Dokument.

//--- getters
   string            getChannelTitle(void);
   string            getTitle(CEasyXmlNode *Node);
   string            getDescription(CEasyXmlNode *Node);
   string            getDate(CEasyXmlNode *Node);

2.7.1 getChannelTitle()

Diese Funktion ruft den aktuellen Kanaltitel des RSS-Dokuments ab.

Sie beginnt zunächst die Gültigkeit des Kanalknoten-Zeigers zu prüfen. Ist dieser gültig, geht sie als Schleife durch alle direkten Nachkommen des Kanalknotens und sucht nach dem Titel-Schlagwort.

Diese Schleife arbeitet mit der m_shift Eigenschaft, um die Anzahl der Kanalknoten-Nachkommen nach denen gesucht wird, zu begrenzen. Ist die Arbeit dieser Funktion nicht erfolgreich, liefert sie NULL.

//+------------------------------------------------------------------+
//| get channel title                                                |
//+------------------------------------------------------------------+
string CRssReader::getChannelTitle(void)
  {
   string ret=NULL;
   if(!CheckPointer(ChannelNode)==POINTER_INVALID)
     {
      for(int i=0;i<m_shift;i++)
        {
         if(ChannelChildNodes[i].getName()=="title")
           {
            ret=ChannelChildNodes[i].getValue();
            break;
           }
         else continue;
        }
     }
//---return value
   return(ret);
  }


2.7.2 getTitle()

Diese Funktion nimmt einen Zeiger auf ein Element-Schlagwort als Eingabe und durchläuft die Nachkommen dieses Schlagworts auf der Suche nach einem Titel-Schlagwort und liefert dann seinen Wert.

Die Funktionen getDescription() und getDate() folgen demselben Format und arbeiten auch ganz ähnlich. Ein erfolgreicher Aufruf der Funktion liefert einen String-Wert, ansonsten wird NULL als Ausgabe geliefert.

//+------------------------------------------------------------------+
//| display title                                                    |
//+------------------------------------------------------------------+
string CRssReader::getTitle(CEasyXmlNode *Node)
  {
   int k=Node.Children().Total();
   string n=NULL;
   for(int i=0;i<k;i++)
     {
      CEasyXmlNode*subNode=Node.Children().At(i);
      if(subNode.getName()=="title")
        {
         n=subNode.getValue();
         break;
        }
      else continue;
     }
   return(n);
  }
//+------------------------------------------------------------------+
//| display description                                              |
//+------------------------------------------------------------------+
string CRssReader::getDescription(CEasyXmlNode *Node)
  {
   int k=Node.Children().Total();
   string n=NULL;
   for(int i=0;i<k;i++)
     {
      CEasyXmlNode*subNode=Node.Children().At(i);
      if(subNode.getName()=="description")
        {
         n=subNode.getValue();
         break;
        }
      else continue;
     }
   return(n);
  }
//+------------------------------------------------------------------+
//| display date                                                     |
//+------------------------------------------------------------------+ 
string CRssReader::getDate(CEasyXmlNode *Node)
  {
   int k=Node.Children().Total();
   string n=NULL;
   for(int i=0;i<k;i++)
     {
      CEasyXmlNode*subNode=Node.Children().At(i);
      if(subNode.getName()=="pubDate")
        {
         n=subNode.getValue();
         break;
        }
      else continue;
     }
   return(n);
  }


2.8 Methoden zur Textformatierung

Diese Funktionen dienen zur Vorbereitung von Text zur Ausgabe als Textobjekte, um einige der Beschränkungen, die Textobjekten innewohnen, zu bewältigen.

 //--- text formating 
   bool              FormatString(string v,string &array[],int n);
   string            removeTags(string _string);
   string            removeSpecialCharacters(string s_tring);
   int               tagPosition(string _string,int w);

2.8.1 FormatString()

Dies ist die Hauptfunktion, die einen aus einem RSS-Dokument extrahierten Text zur Ausgabe in der Anwendung vorbereitet.

Im Grunde nimmt sie einen String-Eingabewert und teilt den Text in Zeilen von "n"-Zeichen auf, wobei "n" ein ganzzahliger Wert der Menge der Zeichen in einer einzigen Textzeile ist. Nach allen "n"-Zeichen im Text, sucht der Code nach einem passenden Ort, um ein Feed-Zeichen der neuen Zeile einzufügen. Anschließend wird der gesamte String-Wert verarbeitet und die Feed-Zeichen der neuen Zeilen werden in den Ursprungstext eingefügt.

Die StringSplit() Funktion dient zur Erzeugung eines Arrays mit Strings, wobei keiner mehr als "n"-Zeichen lang ist. Die Funktion liefert einen boole'schen Wert und auch ein Array mit Stringwerten, die für eine Ausgabe fertig sind.

//+------------------------------------------------------------------+
//| format string for output to text area panel                      |
//+------------------------------------------------------------------+
bool CRssReader::FormatString(string v,string &array[],int n)
  {
   ushort ch[],space,fullstop,comma,semicolon,newlinefeed;
   string _s,_k;
   space=StringGetCharacter(" ",0);
   fullstop=StringGetCharacter(".",0);
   comma=StringGetCharacter(",",0);
   semicolon=StringGetCharacter(";",0);
   newlinefeed=StringGetCharacter("\n",0);
   _k=removeTags(v);
   _s=removeSpecialCharacters(_k);
   int p=StringLen(_s);
   ArrayResize(ch,p+1);
   int d=StringToShortArray(_s,ch,0,-1);
   for(int i=1;i<d;i++)
     {
      int t=i%n;
      if(!t== 0)continue;
      else 
        {
         if(ch[(i/n)*n]==fullstop || ch[(i/n)*n]==semicolon || ch[(i/n)*n]==comma)
           {
            ArrayFill(ch,((i/n)*n)+1,1,newlinefeed);
           }
         else
           {
            for(int k=i;k>=0;k--)
              {
               if(ch[k]==space)
                 {
                  ArrayFill(ch,k,1,newlinefeed);
                  break;
                 }
               else continue;
              }
           }
        }
     }
   _s=ShortArrayToString(ch,0,-1);
   int s=StringSplit(_s,newlinefeed,array);
   if(!s>0)
     {return(false);}
// success 
   return(true);
  }


2.8.2 removeTags()

Diese Funktion wurde notwendig, nachdem ich festgestellt hatte, dass eine ganze Menge RSS-Dokumente innerhalb der XML-Knoten HTML-Schlagworte enthalten.

Einige RSS-Dokumente werden so veröffentlicht, da viele RSS zusammenfassende Anwendungen im Browser arbeiten.

Die Funktion bedient sich eines Stringwerts und sucht innerhalb des Texts nach Schlagworten. Wird ein Schlagwort gefunden, durchläuft die Funktion jedes Zeichen des Texts als Schleife und speichert die Position jedes Eröffnungs- und Schließungs-Schlagwortzeichens im zweidimensionalen Array. Diese Array wird zur Extrahierung des Texts zwischen den Schlagworten benutzt, und der extrahierte String wird geliefert. Werden keine Schlagworte gefunden, wird der Eingabe-String so wie er ist geliefert.

//+------------------------------------------------------------------+
//| remove tags                                                      |
//+------------------------------------------------------------------+
string CRssReader::removeTags(string _string)
  {
   string now=NULL;
   if(StringFind(_string,"<",0)>-1)
     {
      int v=0,a[][2];
      ArrayResize(a,2024);
      for(int i=0;i<StringLen(_string);i++)
        {
         int t=tagPosition(_string,i);
         if(t>0)
           {
            v++;
            a[v-1][0]=i;
            a[v-1][1]=t;
           }
         else continue;
        }
      ArrayResize(a,v);
      for(int i=0;i<v-1;i++)
        {
         now+=StringSubstr(_string,(a[i][1]+1),(a[i+1][0]-(a[i][1]+1)));
        }
     }
   else
     {
      now=_string;
     }
   return(now);
  }

Unten sehen Sie ein teilweises Beispiel so eines Dokuments.

<item>            
    <title>GIGABYTE X99-Gaming G1 WIFI Motherboard Review</title>
    <author>Ian Cutress</author>
    <description><![CDATA[ <p>The gaming motherboard range from a manufacturer is one with a lot of focus in terms of design and function due to the increase in gaming related PC sales. On the Haswell-E side of gaming, GIGABYTE is putting forward the X99-Gaming G1 WIFI at the top of its stack, and this is what we are reviewing today.&nbsp;</p>
<p align="center"><a href='http://dynamic1.anandtech.com/www/delivery/ck.php?n=a1f2f01f&amp;cb=582254849' target='_blank'><img src='http://dynamic1.anandtech.com/www/delivery/avw.php?zoneid=24&amp;cb=582254849&amp;n=a1f2f01f' border='0' alt='' /></a><img src="http://toptenreviews.122.2o7.net/b/ss/tmn-test/1/H.27.3--NS/0" height="1" width="1" border="0" alt="" /></p>]]></description>
    <link>http://www.anandtech.com/show/8788/gigabyte-x99-gaming-g1-wifi-motherboard-review</link>
        <pubDate>Thu, 18 Dec 2014 10:00:00 EDT</pubDate>
        <guid isPermaLink="false">tag:www.anandtech.com,8788:news</guid>
        <category><![CDATA[ Motherboards]]></category>                               
</item>  


2.8.3 removeSpecialCharacters()

Diese Funktion ersetzt einfach String-Konstanten mit dem richtigen Zeichen.

So kann z.B. das 'UND'-Zeichen in einigen XML-Dokumenten als '&' dargestellt werden. Diese Funktion arbeitet mit der eingebauten StringReplace() Funktion, um derartiges zu ersetzen.

//+------------------------------------------------------------------+
//| remove special characters                                        |
//+------------------------------------------------------------------+ 
string CRssReader::removeSpecialCharacters(string s_tring)
  {
   string n=s_tring;
   StringReplace(n,"&amp;","&");
   StringReplace(n,"&#39;","'");
   StringReplace(n,"&nbsp;"," ");
   StringReplace(n,"&ldquo;","\'");
   StringReplace(n,"&rdquo;","\'");
   StringReplace(n,"&quot;","\"");
   StringReplace(n,"&ndash;","-");
   StringReplace(n,"&rsquo;","'");
   StringReplace(n,"&gt;","");
   return(n);
  }


2.8.4 tagPosition()

Dies ist eine Hilfsfunktion, die in der removeTags() Funktion aufgerufen wird. Als Eingabe nimmt sie einen String und eine ganzzahligen Wert auf.

Der ganzzahlige Eingabewert repräsentiert die Position eines Zeichens im String, von wo ab die Funktion nach einem Eröffnungs-Schlagwort-Zeichen zu suchen beginnt, z.B. "<". Wird ein Eröffnungs-Schlagwort gefunden, sucht die Funktion auch nach einem Schließungs-Schlagwort und liefert als Ausgabe die Position des entsprechenden Schließungs-Schlagwort-Zeichens ">". Werden keine Schlagworte gefunden, liefert die Funktion "-1".

//+------------------------------------------------------------------+
//| tag positions                                                    |
//+------------------------------------------------------------------+
int CRssReader::tagPosition(string _string,int w)
  {
   int iClose=-1;
   if(StringCompare("<",StringSubstr(_string,w,StringLen("<")))==0)
     {
      iClose=StringFind(_string,">",w+StringLen("<"));
     }

   return(iClose);
  }


2.9 Methoden zum Umgang mit Ereignissen unabhängiger Kontrollen

Diese Funktionen sind für den Umgang mit erfassten Ereignissen einer spezifischen Kontrolle zuständig.

//--- handlers of the dependent controls events
   void              OnChangeListView(void);
   void              OnObjectEdit(void);
   void              OnClickButton1(void);
   void              OnClickButton2(void);
  };

2.9.1 OnChangeListView()

Dies ist eine Ereignis-Handler Funktion und wird immer dann aufgerufen, wenn eines der Listenelemente im Listenansicht-Bereich der Anwendung angeklickt wird.

Die Funktion ist für die Aktivierung der Ansicht der Beschreibungszusammenfassung für bestimmten Content zuständig, auf den im RSS-Dokument verwiesen wird.

Die Funktion leert die Abschnitte 'Textbereich' und 'Titelbereich' von Text, ruft neue Daten aus dem Dokumentenbaum ab und bereitet sie zur Ausgabe vor. All dies passiert nur, wenn das ChannelChildnodes Array nicht leer ist.

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CRssReader::OnChangeListView(void)
  {
   int a=0,k=0,l=0;
   a=m_listview.Current()+m_shift;
   if(ArraySize(ChannelChildNodes)>a)
     {
      if(m_titleview.ItemsClear())
        {
         if(!FormatString(getTitle(ChannelChildNodes[a]),m_titleareaoutput,55))
           {
            return;
           }
         else
         if(ArraySize(m_titleareaoutput)>0)
           {
            for(l=0;l<ArraySize(m_titleareaoutput);l++)
              {
               m_titleview.AddItem(removeSpecialCharacters(m_titleareaoutput[l]));
              }
           }
        }
      if(m_textview.ItemsClear())
        {
         if(!FormatString(getDescription(ChannelChildNodes[a]),m_textareaoutput,35))
            return;
         else
         if(ArraySize(m_textareaoutput)>0)
           {
            for(k=0;k<ArraySize(m_textareaoutput);k++)
              {
               m_textview.AddItem(m_textareaoutput[k]);
              }
            m_textview.AddItem(" ");
            m_textview.AddItem("Date|"+getDate(ChannelChildNodes[a]));
           }
         else return;
        }
     }
  }


2.9.2 OnObjectEdit()

Die Handler-Funktion wird immer dann aufgerufen, wenn ein Benutzer mit der Eingabe von Text im Eingabebereich fertig ist.

Die Funktion ruft die LoadDocument() Methode auf. Ist der Download erfolgreich, wir der Text aus der gesamten Anwendung entfernt. Anschließend wird die Beschriftung geändert und der neue Content in den Abschnitt 'Listenansicht-Bereich' ausgegeben.

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CRssReader::OnObjectEdit(void)
  {
   string f=m_edit.Text();
   if(StringLen(f)>0)
     {
      if(ArraySize(ChannelChildNodes)<1)
        {
         CDialog::Caption("Loading...");
         if(LoadDocument(f))
           {
            if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle())))
               Print("error changing caption");
            if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear())
              {
               for(int i=0;i<ItemNodesTotal()-1;i++)
                 {
                  if(!m_listview.AddItem(removeSpecialCharacters(IntegerToString(i+1)+"."+getTitle(ChannelChildNodes[i+m_shift]))))
                    {
                     Print("can not add item to listview area");
                     return;
                    }
                 }
              }
            else
              {
               Print("text area/listview area not cleared");
               return;
              }
           }
         else return;
        }
      else
        {
         FreeDocumentTree();
         CDialog::Caption("Loading new RSS Feed...");
         if(LoadDocument(f))
           {
            if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle())))
               Print("error changing caption");
            if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear())
              {
               for(int i=0;i<ItemNodesTotal()-1;i++)
                 {
                  if(!m_listview.AddItem(removeSpecialCharacters(IntegerToString(i+1)+"."+getTitle(ChannelChildNodes[i+m_shift]))))
                    {
                     Print("can not add item to listview area");
                     return;
                    }
                 }
              }
            else
              {
               Print("text area/listview area not cleared");
               return;
              }
           }
         else return;
        }
     }
   else return;
  }


2.9.3 OnClickSchaltfläche1/2()

Diese Handler werden aufgerufen, sobald ein Benutzer entweder auf 'Zurücksetzen' oder auf 'Feed nach Updates überprüfen' klickt.

Durch Anklicken von 'Zurücksetzen' wird der Anwendungsdialog aktualisiert und zwar zurück auf den Status als der Expert Advisor zum ersten Mal gestartet wurde.

Durch Anklicken von 'Feed nach Updates überprüfen' wird ein erneuter Aufruf der LoadDocument() Methode in Gang gesetzt und die Daten des RSS-Feeds werden heruntergeladen und somit der Abschnitt 'Listenansichts-Bereich' aktualisiert.

//+------------------------------------------------------------------+
//| Event handler  refresh the app dialogue                          |
//+------------------------------------------------------------------+   
void CRssReader::OnClickButton1(void)
  {
   if(ArraySize(ChannelChildNodes)<1)
     {
      if(!m_edit.Text("Enter the web address of an Rss feed"))
         Print("error changing edit text");
      if(!CDialog::Caption("RSSReader"))
         Print("error changing caption");
      if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear())
        {
         for(int i=0;i<20;i++)
           {
            if(!m_listview.AddItem(" "))
               Print("error adding to listview");
           }
         m_listview.VScrolled(true);
         for(int i=0;i<1;i++)
           {
            m_textview.AddItem(" ");
           }
         m_textview.VScrolled(true);
         for(int i=0;i<2;i++)
           {
            m_titleview.AddItem(" ");
           }
         return;
        }
     }
   else
     {
      FreeDocumentTree();
      if(!m_edit.Text("Enter the web address of an Rss feed"))
         Print("error changing edit text");
      if(!CDialog::Caption("RSSReader"))
         Print("error changing caption");
      if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear())
        {
         for(int i=0;i<20;i++)
           {
            if(!m_listview.AddItem(" "))
               Print("error adding to listview");
           }
         m_listview.VScrolled(true);
         for(int i=0;i<1;i++)
           {
            m_textview.AddItem(" ");
           }
         m_textview.VScrolled(true);
         for(int i=0;i<2;i++)
           {
            m_titleview.AddItem(" ");
           }
         return;
        }
     }
  }
//+------------------------------------------------------------------+
//| Event handler  update current feed                               |
//+------------------------------------------------------------------+ 
void CRssReader::OnClickButton2(void)
  {
   string f=m_rssurl;
   if(ArraySize(ChannelChildNodes)<1)
      return;
   else
     {
      FreeDocumentTree();
      CDialog::Caption("Checking for RSS Feed update...");
      if(LoadDocument(f))
        {
         if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle())))
            Print("error changing caption");
         if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear())
           {
            for(int i=0;i<ItemNodesTotal()-1;i++)
              {
               if(!m_listview.AddItem(removeSpecialCharacters(IntegerToString(i+1)+"."+getTitle(ChannelChildNodes[i+m_shift]))))
                 {
                  Print("can not add item to listview area");
                  return;
                 }
              }
           }
         else
           {
            Print("text area/listview area not cleared");
            return;
           }
        }
      else return;
     }
  }

Damit ist die Definition der CRssReader Klasse abgeschlossen.


2.10 Implementierung der CRssReader Klasse

//+------------------------------------------------------------------+
//| Class CRssReader                                                 |
//| Usage: main class for the RSS application                        |
//+------------------------------------------------------------------+
class CRssReader : public CAppDialog
  {
private:
   int               m_shift;                   // index of first item tag
   string            m_rssurl;                  // copy of web address of last feed 
   string            m_textareaoutput[];        // array of strings prepared for output to the text area panel
   string            m_titleareaoutput[];       // array of strings prepared for output to title area panel
   CButton           m_button1;                 // the button object
   CButton           m_button2;                 // the button object      
   CEdit             m_edit;                    // input panel
   CTitleArea        m_titleview;               // the display field object
   CListViewArea     m_listview;                // the list object
   CTextArea         m_textview;                // text area object
   CEasyXml          m_xmldocument;             // xml document object
   CEasyXmlNode     *RssNode;                   // root node object
   CEasyXmlNode     *ChannelNode;               // channel node object
   CEasyXmlNode     *ChannelChildNodes[];       // array of channel child node objects

public:
                     CRssReader(void);
                    ~CRssReader(void);
   //--- create
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   //--- chart event handler
   virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);

protected:
   // --- creating controls
   bool              CreateEdit(void);
   bool              CreateButton1(void);
   bool              CreateButton2(void);
   bool              CreateTitleView(void);
   bool              CreateListView(void);
   bool              CreateTextView(void);
   // --- rss document processing
   bool              LoadDocument(string filename);
   int               ItemNodesTotal(void);
   void              FreeDocumentTree(void);
   //--- getters
   string            getChannelTitle(void);
   string            getTitle(CEasyXmlNode *Node);
   string            getDescription(CEasyXmlNode *Node);
   string            getDate(CEasyXmlNode *Node);
   //--- text formating 
   bool              FormatString(string v,string &array[],int n);
   string            removeTags(string _string);
   string            removeSpecialCharacters(string s_tring);
   int               tagPosition(string _string,int w);
   //--- handlers of the dependent controls events
   void              OnChangeListView(void);
   void              OnObjectEdit(void);
   void              OnClickButton1(void);
   void              OnClickButton2(void);
  };
//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CRssReader)
ON_EVENT(ON_CHANGE,m_listview,OnChangeListView)
ON_EVENT(ON_END_EDIT,m_edit,OnObjectEdit)
ON_EVENT(ON_CLICK,m_button1,OnClickButton1)
ON_EVENT(ON_CLICK,m_button2,OnClickButton2)
EVENT_MAP_END(CAppDialog)
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CRssReader::CRssReader(void)
  {

  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CRssReader::~CRssReader(void)
  {
  }
//+------------------------------------------------------------------+
//| Create                                                           |
//+------------------------------------------------------------------+
bool CRssReader::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2)
  {
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
      return(false);
//--- create dependent controls
   if(!CreateEdit())
      return(false);
   if(!CreateButton1())
      return(false);
   if(!CreateButton2())
      return(false);
   if(!CreateTitleView())
      return(false);
   if(!CreateListView())
      return(false);
   if(!CreateTextView())
      return(false);
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Create the display field                                         |
//+------------------------------------------------------------------+
bool CRssReader::CreateEdit(void)
  {
//--- coordinates
   int x1=INDENT_LEFT;
   int y1=INDENT_TOP;
   int x2=ClientAreaWidth()-INDENT_RIGHT;
   int y2=y1+EDIT_HEIGHT;
//--- create
   if(!m_edit.Create(m_chart_id,m_name+"Edit",m_subwin,x1,y1,x2,y2))
      return(false);
   if(!m_edit.Text("Please enter the web address of an Rss feed"))
      return(false);
   if(!m_edit.ReadOnly(false))
      return(false);
   if(!Add(m_edit))
      return(false);
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Create button 1                                                  |
//+------------------------------------------------------------------+ 
bool CRssReader::CreateButton1(void)
  {
//--- coordinates
   int x1=INDENT_LEFT;
   int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y);
   int x2=x1+BUTTON_WIDTH;
   int y2=y1+BUTTON_HEIGHT;
//--- create
   if(!m_button1.Create(m_chart_id,m_name+"Button1",m_subwin,x1,y1,x2,y2))
      return(false);
   if(!m_button1.Text("Reset"))
      return(false);
   if(!m_button1.Font("Comic Sans MS"))
      return(false);
   if(!m_button1.FontSize(8))
      return(false);
   if(!m_button1.Color(clrWhite))
      return(false);
   if(!m_button1.ColorBackground(clrBlack))
      return(false);
   if(!m_button1.ColorBorder(clrBlack))
      return(false);
   if(!m_button1.Pressed(true))
      return(false);
   if(!Add(m_button1))
      return(false);
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Create button 2                                                  |
//+------------------------------------------------------------------+ 
bool CRssReader::CreateButton2(void)
  {
//--- coordinates
   int x1=(ClientAreaWidth()-INDENT_RIGHT)-BUTTON_WIDTH;
   int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y);
   int x2=ClientAreaWidth()-INDENT_RIGHT;
   int y2=y1+BUTTON_HEIGHT;
//--- create
   if(!m_button2.Create(m_chart_id,m_name+"Button2",m_subwin,x1,y1,x2,y2))
      return(false);
   if(!m_button2.Text("Update current feed"))
      return(false);
   if(!m_button2.Font("Comic Sans MS"))
      return(false);
   if(!m_button2.FontSize(8))
      return(false);
   if(!m_button2.Color(clrWhite))
      return(false);
   if(!m_button2.ColorBackground(clrBlack))
      return(false);
   if(!m_button2.ColorBorder(clrBlack))
      return(false);
   if(!m_button2.Pressed(true))
      return(false);
   if(!Add(m_button2))
      return(false);
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Create the display field                                         |
//+------------------------------------------------------------------+
bool CRssReader::CreateTitleView(void)
  {
//--- coordinates
   int x1=INDENT_LEFT;
   int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y)+BUTTON_HEIGHT+CONTROLS_GAP_Y;
   int x2=ClientAreaWidth()-INDENT_RIGHT;
   int y2=y1+(EDIT_HEIGHT*2);
   m_titleview.Current();
//--- create 
   if(!m_titleview.Create(m_chart_id,m_name+"TitleView",m_subwin,x1,y1,x2,y2))
     {
      Print("error creating title view");
      return(false);
     }
   else
     {
      for(int i=0;i<2;i++)
        {
         m_titleview.AddItem(" ");
        }
     }
   if(!Add(m_titleview))
     {
      Print("error adding title view");
      return(false);
     }
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Create the "ListView" element                                    |
//+------------------------------------------------------------------+
bool CRssReader::CreateListView(void)
  {

//--- coordinates
   int x1=INDENT_LEFT;
   int y1=INDENT_TOP+((EDIT_HEIGHT+CONTROLS_GAP_Y)*2)+20+TEXTAREA_HEIGHT+CONTROLS_GAP_Y+BUTTON_HEIGHT+CONTROLS_GAP_Y;
   int x2=ClientAreaWidth()-INDENT_RIGHT;
   int y2=y1+LIST_HEIGHT;
//--- create
   if(!m_listview.Create(m_chart_id,m_name+"ListView",m_subwin,x1,y1,x2,y2))
      return(false);
   if(!Add(m_listview))
      return(false);
//--- fill out with strings
   for(int i=0;i<20;i++)
      if(!m_listview.AddItem(" "))
         return(false);
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Create the display field                                         |
//+------------------------------------------------------------------+
bool CRssReader::CreateTextView(void)
  {
//--- coordinates
   int x1=INDENT_LEFT;
   int y1=INDENT_TOP+((EDIT_HEIGHT+CONTROLS_GAP_Y)*2)+20+BUTTON_HEIGHT+CONTROLS_GAP_Y;
   int x2=ClientAreaWidth()-INDENT_RIGHT;
   int y2=y1+TEXTAREA_HEIGHT;
   m_textview.Current();
//--- create 
   if(!m_textview.Create(m_chart_id,m_name+"TextArea",m_subwin,x1,y1,x2,y2))
     {
      Print("error creating text area view");
      return(false);
     }
   else
     {
      for(int i=0;i<1;i++)
        {
         m_textview.AddItem(" ");
        }
      m_textview.VScrolled(true);
      ChartRedraw();
     }
   if(!Add(m_textview))
     {
      Print("error adding text area view");
      return(false);
     }
//----success      
   return(true);
  }
//+------------------------------------------------------------------+
//|   load document                                                  |
//+------------------------------------------------------------------+
bool CRssReader::LoadDocument(string filename)
  {
   if(!m_xmldocument.loadXmlFromUrlWebReq(filename))
     {
      m_textview.ItemsClear();
      m_listview.ItemsClear();
      m_titleview.ItemsClear();
      CDialog::Caption("Failed to load Feed");
      if(!m_textview.AddItem(m_xmldocument.GetErrorMsg()))
         Print("error displaying error message");
      return(false);
     }
   else
     {
      m_rssurl=filename;
      RssNode=m_xmldocument.getDocumentRoot();
      ChannelNode=RssNode.FirstChild();
      if(CheckPointer(RssNode)==POINTER_INVALID || CheckPointer(ChannelNode)==POINTER_INVALID)
         return(false);
     }
   ArrayResize(ChannelChildNodes,ChannelNode.Children().Total());
   for(int i=0;i<ChannelNode.Children().Total();i++)
     {
      ChannelChildNodes[i]=ChannelNode.Children().At(i);
     }
   m_shift=ChannelNode.Children().Total()-ItemNodesTotal();
   return(true);
  }
//+------------------------------------------------------------------+
//| function counts the number of item tags in document              |
//+------------------------------------------------------------------+
int CRssReader::ItemNodesTotal(void)
  {
   int t=0;
   for(int i=0;i<ChannelNode.Children().Total();i++)
     {
      if(ChannelChildNodes[i].getName()=="item")
        {
         t++;
        }
      else continue;
     }
   return(t);
  }
//+------------------------------------------------------------------+
//| free document tree and reset pointer values                      |
//+------------------------------------------------------------------+ 
void CRssReader::FreeDocumentTree(void)
  {
   ChannelNode.Children().Shutdown();
   ArrayFree(ChannelChildNodes);
   RssNode.Children().Shutdown();
   m_xmldocument.Clear();
   m_shift=0;
   RssNode=NULL;
   ChannelNode=NULL;
  }
//+------------------------------------------------------------------+
//| get channel title                                                |
//+------------------------------------------------------------------+
string CRssReader::getChannelTitle(void)
  {
   string ret=NULL;
   if(!CheckPointer(ChannelNode)==POINTER_INVALID)
     {
      for(int i=0;i<m_shift;i++)
        {
         if(ChannelChildNodes[i].getName()=="title")
           {
            ret=ChannelChildNodes[i].getValue();
            break;
           }
         else continue;
        }
     }
//---return value
   return(ret);
  }
//+------------------------------------------------------------------+
//| display title                                                    |
//+------------------------------------------------------------------+
string CRssReader::getTitle(CEasyXmlNode *Node)
  {
   int k=Node.Children().Total();
   string n=NULL;
   for(int i=0;i<k;i++)
     {
      CEasyXmlNode*subNode=Node.Children().At(i);
      if(subNode.getName()=="title")
        {
         n=subNode.getValue();
         break;
        }
      else continue;
     }
   return(n);
  }
//+------------------------------------------------------------------+
//| display description                                              |
//+------------------------------------------------------------------+
string CRssReader::getDescription(CEasyXmlNode *Node)
  {
   int k=Node.Children().Total();
   string n=NULL;
   for(int i=0;i<k;i++)
     {
      CEasyXmlNode*subNode=Node.Children().At(i);
      if(subNode.getName()=="description")
        {
         n=subNode.getValue();
         break;
        }
      else continue;
     }
   return(n);
  }
//+------------------------------------------------------------------+
//| display date                                                     |
//+------------------------------------------------------------------+ 
string CRssReader::getDate(CEasyXmlNode *Node)
  {
   int k=Node.Children().Total();
   string n=NULL;
   for(int i=0;i<k;i++)
     {
      CEasyXmlNode*subNode=Node.Children().At(i);
      if(subNode.getName()=="pubDate")
        {
         n=subNode.getValue();
         break;
        }
      else continue;
     }
   return(n);
  }
//+------------------------------------------------------------------+
//| format string for output to text area panel                      |
//+------------------------------------------------------------------+
bool CRssReader::FormatString(string v,string &array[],int n)
  {
   ushort ch[],space,fullstop,comma,semicolon,newlinefeed;
   string _s,_k;
   space=StringGetCharacter(" ",0);
   fullstop=StringGetCharacter(".",0);
   comma=StringGetCharacter(",",0);
   semicolon=StringGetCharacter(";",0);
   newlinefeed=StringGetCharacter("\n",0);
   _k=removeTags(v);
   _s=removeSpecialCharacters(_k);
   int p=StringLen(_s);
   ArrayResize(ch,p+1);
   int d=StringToShortArray(_s,ch,0,-1);
   for(int i=1;i<d;i++)
     {
      int t=i%n;
      if(!t== 0)continue;
      else 
        {
         if(ch[(i/n)*n]==fullstop || ch[(i/n)*n]==semicolon || ch[(i/n)*n]==comma)
           {
            ArrayFill(ch,((i/n)*n)+1,1,newlinefeed);
           }
         else
           {
            for(int k=i;k>=0;k--)
              {
               if(ch[k]==space)
                 {
                  ArrayFill(ch,k,1,newlinefeed);
                  break;
                 }
               else continue;
              }
           }
        }
     }
   _s=ShortArrayToString(ch,0,-1);
   int s=StringSplit(_s,newlinefeed,array);
   if(!s>0)
     {return(false);}
// success 
   return(true);
  }
//+------------------------------------------------------------------+
//| remove special characters                                        |
//+------------------------------------------------------------------+ 
string CRssReader::removeSpecialCharacters(string s_tring)
  {
   string n=s_tring;
   StringReplace(n,"&amp;","&");
   StringReplace(n,"&#39;","'");
   StringReplace(n,"&nbsp;"," ");
   StringReplace(n,"&ldquo;","\'");
   StringReplace(n,"&rdquo;","\'");
   StringReplace(n,"&quot;","\"");
   StringReplace(n,"&ndash;","-");
   StringReplace(n,"&rsquo;","'");
   StringReplace(n,"&gt;","");
   return(n);
  }
//+------------------------------------------------------------------+
//| remove tags                                                      |
//+------------------------------------------------------------------+
string CRssReader::removeTags(string _string)
  {
   string now=NULL;
   if(StringFind(_string,"<",0)>-1)
     {
      int v=0,a[][2];
      ArrayResize(a,2024);
      for(int i=0;i<StringLen(_string);i++)
        {
         int t=tagPosition(_string,i);
         if(t>0)
           {
            v++;
            a[v-1][0]=i;
            a[v-1][1]=t;
           }
         else continue;
        }
      ArrayResize(a,v);
      for(int i=0;i<v-1;i++)
        {
         now+=StringSubstr(_string,(a[i][1]+1),(a[i+1][0]-(a[i][1]+1)));
        }
     }
   else
     {
      now=_string;
     }
   return(now);
  }
//+------------------------------------------------------------------+
//| tag positions                                                    |
//+------------------------------------------------------------------+
int CRssReader::tagPosition(string _string,int w)
  {
   int iClose=-1;
   if(StringCompare("<",StringSubstr(_string,w,StringLen("<")))==0)
     {
      iClose=StringFind(_string,">",w+StringLen("<"));
     }

   return(iClose);
  }
//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CRssReader::OnChangeListView(void)
  {
   int a=0,k=0,l=0;
   a=m_listview.Current()+m_shift;
   if(ArraySize(ChannelChildNodes)>a)
     {
      if(m_titleview.ItemsClear())
        {
         if(!FormatString(getTitle(ChannelChildNodes[a]),m_titleareaoutput,55))
           {
            return;
           }
         else
         if(ArraySize(m_titleareaoutput)>0)
           {
            for(l=0;l<ArraySize(m_titleareaoutput);l++)
              {
               m_titleview.AddItem(removeSpecialCharacters(m_titleareaoutput[l]));
              }
           }
        }
      if(m_textview.ItemsClear())
        {
         if(!FormatString(getDescription(ChannelChildNodes[a]),m_textareaoutput,35))
            return;
         else
         if(ArraySize(m_textareaoutput)>0)
           {
            for(k=0;k<ArraySize(m_textareaoutput);k++)
              {
               m_textview.AddItem(m_textareaoutput[k]);
              }
            m_textview.AddItem(" ");
            m_textview.AddItem("Date|"+getDate(ChannelChildNodes[a]));
           }
         else return;
        }
     }
  }
//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CRssReader::OnObjectEdit(void)
  {
   string f=m_edit.Text();
   if(StringLen(f)>0)
     {
      if(ArraySize(ChannelChildNodes)<1)
        {
         CDialog::Caption("Loading...");
         if(LoadDocument(f))
           {
            if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle())))
               Print("error changing caption");
            if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear())
              {
               for(int i=0;i<ItemNodesTotal()-1;i++)
                 {
                  if(!m_listview.AddItem(removeSpecialCharacters(IntegerToString(i+1)+"."+getTitle(ChannelChildNodes[i+m_shift]))))
                    {
                     Print("can not add item to listview area");
                     return;
                    }
                 }
              }
            else
              {
               Print("text area/listview area not cleared");
               return;
              }
           }
         else return;
        }
      else
        {
         FreeDocumentTree();
         CDialog::Caption("Loading new RSS Feed...");
         if(LoadDocument(f))
           {
            if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle())))
               Print("error changing caption");
            if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear())
              {
               for(int i=0;i<ItemNodesTotal()-1;i++)
                 {
                  if(!m_listview.AddItem(removeSpecialCharacters(IntegerToString(i+1)+"."+getTitle(ChannelChildNodes[i+m_shift]))))
                    {
                     Print("can not add item to listview area");
                     return;
                    }
                 }
              }
            else
              {
               Print("text area/listview area not cleared");
               return;
              }
           }
         else return;
        }
     }
   else return;
  }
//+------------------------------------------------------------------+
//| Event handler  refresh the app dialogue                          |
//+------------------------------------------------------------------+   
void CRssReader::OnClickButton1(void)
  {
   if(ArraySize(ChannelChildNodes)<1)
     {
      if(!m_edit.Text("Enter the web address of an Rss feed"))
         Print("error changing edit text");
      if(!CDialog::Caption("RSSReader"))
         Print("error changing caption");
      if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear())
        {
         for(int i=0;i<20;i++)
           {
            if(!m_listview.AddItem(" "))
               Print("error adding to listview");
           }
         m_listview.VScrolled(true);
         for(int i=0;i<1;i++)
           {
            m_textview.AddItem(" ");
           }
         m_textview.VScrolled(true);
         for(int i=0;i<2;i++)
           {
            m_titleview.AddItem(" ");
           }
         return;
        }
     }
   else
     {
      FreeDocumentTree();
      if(!m_edit.Text("Enter the web address of an Rss feed"))
         Print("error changing edit text");
      if(!CDialog::Caption("RSSReader"))
         Print("error changing caption");
      if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear())
        {
         for(int i=0;i<20;i++)
           {
            if(!m_listview.AddItem(" "))
               Print("error adding to listview");
           }
         m_listview.VScrolled(true);
         for(int i=0;i<1;i++)
           {
            m_textview.AddItem(" ");
           }
         m_textview.VScrolled(true);
         for(int i=0;i<2;i++)
           {
            m_titleview.AddItem(" ");
           }
         return;
        }
     }
  }
//+------------------------------------------------------------------+
//| Event handler  update current feed                               |
//+------------------------------------------------------------------+ 
void CRssReader::OnClickButton2(void)
  {
   string f=m_rssurl;
   if(ArraySize(ChannelChildNodes)<1)
      return;
   else
     {
      FreeDocumentTree();
      CDialog::Caption("Checking for RSS Feed update...");
      if(LoadDocument(f))
        {
         if(!CDialog::Caption(removeSpecialCharacters(getChannelTitle())))
            Print("error changing caption");
         if(m_textview.ItemsClear() && m_listview.ItemsClear() && m_titleview.ItemsClear())
           {
            for(int i=0;i<ItemNodesTotal()-1;i++)
              {
               if(!m_listview.AddItem(removeSpecialCharacters(IntegerToString(i+1)+"."+getTitle(ChannelChildNodes[i+m_shift]))))
                 {
                  Print("can not add item to listview area");
                  return;
                 }
              }
           }
         else
           {
            Print("text area/listview area not cleared");
            return;
           }
        }
      else return;
     }
  }

Sie kann nun im Code des Expert Advisors benutzt werden.


2.11 Der Code des Expert Advisors

Der Expert Advisor besitzt keine Eingabevariablen, da die Anwendung ja komplett interaktiv sein soll.

Zuerst deklarieren wir eine globale Variable, die eine Instanz der CRssReader-Klasse ist. In der OnInit() Funktion initialisieren wir den Anwendungsdialog mit einem Aufruf zur hauptsächlichen Create()-Methode. Ist dies erfolgreich, wird die Run() Methode einer Vorgängerklasse aufgerufen.

In der OnDeinit() Funktion wird die Destroy() Methode der übergeordneten Klasse aufgerufen, um die komplette Anwendung zu löschen und den Expert Advisor vom Chart zu entfernen.

Die OnChartEvent() Funktion enthält einen Aufruf zu einer Vorgängermethode der CRssReader Klasse, die sich um die Verarbeitung aller Ereignisse kümmert.

//Expert Advisor code begins here
//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CRssReader ExtDialog;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create application dialog
   if(!ExtDialog.Create(0,"RSSReader",0,20,20,518,394))
      return(INIT_FAILED);
//--- run application
   ExtDialog.Run();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   ExtDialog.Destroy(reason);
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   ExtDialog.ChartEvent(id,lparam,dparam,sparam);
  }

Anschließend muss der Code erstellt werden und das Programm ist einsatzbereit.

Wird dann der RssReader.mq5 Expert Advisor aufs Chart geladen, erscheint ein leerer Anwendungsdialog, der folgendermaßen aussieht:

Abb. 2 Screenshot des leeren Anwendungsdialogs des RssReader Expert Advisor

Abb. 2 Screenshot des leeren Anwendungsdialogs des RssReader Expert Advisor

Geben Sie eine Internetadresse ein und der RSS-Content wird in den Anwendungsdialog geladen - so wie im Bild unten gezeigt:

Abb. 3 Der RssReader EA bei der Arbeit im Terminal

Abb. 3 Der RssReader EA bei der Arbeit im Terminal

Ich habe das Programm mit einer großen Bandbreite an RSS-Feeds getestet. Das einzige Problem, das mir aufgefallen ist, hatte mit der Anzeige unerwünschter Zeichen zu tun - meist die Folge von RSS-Dokumenten, die Zeichen enthielten, die sonst nur in HTML-Dokumenten vorkommen.

Außerdem habe ich bemerkt, dass sich, bei der Änderung des Zeitraums auf einem Chart bei laufender Anwendung, der EA re-initialisiert, sodass das Ergebnis in den Anwendungskontrollen nicht ordentlich gezeichnet werden kann.

Dieses Phänomen konnte ich nicht bereinigen, daher ist mein Tipp: Den Chart-Zeitraum am besten nicht ändern, während das RSS Reader Programm läuft.


Fazit

Wir haben nun die Erzeugung einer komplett interaktiven RSS Reader Anwendung für MetaTrader 5 mit Hilfe von Objekt-orientierten Programmiertechniken abgeschlossen.

Dieser Anwendung könnten noch eine ganze Menge weitere Features hinzugefügt werden, und auch die Benutzerschnittstelle kann noch auf viele weitere Arten angeordnet werden. Ich hoffe, dass alle, mit mehr Erfahrung im Design von grafischen Benutzerschnittstellen, die Anwendung noch weiter verbessern und diese Verbesserungen auch mit anderen teilen.

PS: Bitte beachten Sie: Die easyxml.mqh-Datei, die hier für den Download bereit steht, ist nicht dieselbe, wie die in der CodeBase, da sie alle, in diesem Beitrag angesprochenen Modifizierungen enthält. Alle notwendigen 'Includes' finden Sie in der RssReader.zip Datei.


Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/1589

Beigefügte Dateien |
easyxml.mqh (25.66 KB)
easyxmlnode.mqh (7.67 KB)
ListViewArea.mqh (19.89 KB)
TextArea.mqh (13.47 KB)
TitleArea.mqh (13.56 KB)
RssReader.mq5 (27.83 KB)
RssReader.zip (20.49 KB)
Das MQL5-Kochbuch: ОСО-Orders Das MQL5-Kochbuch: ОСО-Orders
Die Handelsaktivitäten jedes Händlers haben immer mit verschiedenen Mechanismen und Verflechtungen zu tun, einschließlich Zusammenhängen bei Orders. Dieser Beitrag schlägt eine Lösung zur Verarbeitung von OCO-Orders vor. Hierbei spielen Standard Library-Klassen sowie auch neue Datentypen, die darin erzeugt werden, eine große Rolle.
Sichern Ihres Expert Advisors beim Handel auf der Moskauer Börse Sichern Ihres Expert Advisors beim Handel auf der Moskauer Börse
Dieser Beitrag behandelt Handelsmethoden, mit denen die Sicherheit von Handelsoperationen auf Aktienbörsen und Märkten mit geringer Liquidität gewährleistet wird, anhand des Beispiels des Terminmarktes der Moskauer Börse. Er liefert einen praktischen Ansatz für die Handelstheorie aus dem Beitrag "Grundlagen der Preisbildung von Börsen anhand des Beispiels des Terminmarktes der Moskauer Börse".
Die Betrachtung der CCanvas-Klasse. Wie man transparente Objekte zeichnet Die Betrachtung der CCanvas-Klasse. Wie man transparente Objekte zeichnet
Sie wollen mehr als nur komische Grafiken von gleitenden Mittelwerten? Sie möchten etwas Schöneres in Ihrem Terminal abbilden, als nur ein schlichtes, gefülltes Rechteck? Das geht! Im Terminal kann man nämlich tatsächliche attraktive Grafiken zeichnen. Und zwar durch Implementierung der CСanvas-Klasse, die zur Erzeugung von individuell angepassten Grafiken benutzt wird. Mit dieser Klasse können Sie Transparenz umsetzen, Farben mischen und sogar den Anschein von Transparenz durch Überlappung und Ineinanderlaufen von Farben erreichen.
Zeichnen von Messuhren mithilfe der Klasse CCanvas Zeichnen von Messuhren mithilfe der Klasse CCanvas
Wir finden Messuhren in Autos und Flugzeugen, in der industriellen Fertigung und in unserem Alltag. Sie werden in allen möglichen Bereichen eingesetzt, die eine schnelle Reaktion auf das Verhalten eines kontrollierten Werts erfordern. Dieser Beitrag beschreibt die Bibliothek der Messuhren für MetaTrader 5.