
MQL für Anfänger: Wie man Objektklassen entwirft und baut
Einführung in Objekt-orientiertes Programmieren (OOP)
Anfängerfrage: Wenn man nur eine vage Ahnung von prozeduralem Programmieren hat, kann man dann OOP beherrschen und es zum Schreiben von automatisierten Handelsstrategien verwenden? Oder übersteigt diese Aufgabe die Kenntnisse des gewöhnlichen Anwenders?
Im Großen und Ganzen ist es möglich, die Objekt-orientierte Programmiersprache zum Schreiben eines MQL5 Expert Advisors oder Indikators zu benutzen, ohne dazu die Prinzipien des Objekt-orientierten Programmierens anwenden zu müssen. Sie müssen nicht unbedingt neue Technologien in Ihren Entwicklungen verwenden. Wählen Sie den Weg, der Ihnen am einfachsten erscheint. Zudem kann die Anwendung des OOP darüber hinaus nicht die Rentabilität von Handelsrobotern garantieren, die Sie erzeugen.
Dennoch ermöglicht der Übergang zu einem neuen (Objekt-orientierten) Ansatz die Tür zur Anwendung komplexerer adaptiver Handelsstrategie-mathematischer Modelle auf ihre Expert Advisor, die auf externe Veränderungen reagieren und sich mit dem Markt synchronisieren.
Also sehen wir uns die Technologien mal an, auf denen das OOP beruht:
- Ereignisse
- Objektklassen
Ereignisse sind die primäre Basis von OOP. Die gesamte Programmlogik ist auf der Verarbeitung von konstant ankommenden Ereignissen aufgebaut. Die entsprechenden Reaktionen auf diese Ereignisse werden in den Objektklassen festgelegt und beschrieben. Anders gesagt: ein Klassenobjekt arbeitet durch Abfangen und Verarbeiten des Ereignis-Flows.
Die zweite Basis ist die Klasse der Objekte, die ihrerseits wiederum auf "drei Säulen" ruht:
- Verkapselung - Schutz der Klasse auf Basis eines "Black-Box" Prinzips: das Objekt reagiert auf Ereignisse, aber seine tatsächliche Implementierung bleibt unbekannt.
- Vererbung - Möglichkeit der Erzeugung einer neuen Klasse aus einer bestehenden unter Beibehaltung aller Eigenschaften und Methoden der "Vorgänger"-Klasse.
- Polymorphismus - Möglichkeit der Veränderung der Implementierung einer ererbten Methode in eine "Nachfolger"-Klasse.
Basiskonzepte kann am besten im Expert Advisor-Code darstellen.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() // OnInit event processing { return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) // OnDeInit event processing { } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() // OnTick event processing { } //+------------------------------------------------------------------+ //| Expert Timer function | //+------------------------------------------------------------------+ void OnTimer() // OnTimer event processing { } //+------------------------------------------------------------------+ //| Expert Chart event function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, // OnChartEvent event processing const long &lparam, const double &dparam, const string &sparam) { }
class CNew:public CObject { private: int X,Y; void EditXY(); protected: bool on_event; //events processing flag public: // Class constructor void CNew(); // OnChart event processing method virtual void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); };
private: int X,Y; void EditXY();
class CNew: public CObject
// OnChart event processing method virtual void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
Der virtuelle Modifikator dieser Methode bedeutet, dass der OnEvent Handler außer Kraft gesetzt werden kann, doch der Name der Methode bleibt in unserem Fall der gleiche wie der der Vorgänger-Klasse.
2. Klassen entwerfen
Einer der bedeutendsten Vorteile der OOP ist seine Ausbaufähigkeit - d.h. das bestehende System kann mit neuen Komponenten arbeiten, ohne dass man diese verändern muss. Zu diesem Zeitpunkt können neue Komponenten hinzugefügt werden.
Sehen wir uns den Designvorgang an, indem wir ein virtuelles Designprogramm von MasterWindows Klassen für MQL5 erzeugen.
2,1. Phase I: Projektentwurf
Der Designprozess beginnt mit einer Skizze, die mit Bleistift auf ein Blatt Papier gezeichnet wird. Dies ist einer der anspruchsvollsten und aufregendsten Momente beim Programmieren. Wir müssen hier nicht nur den Dialog zwischen dem Programm und dem Benutzer (Interface) bedenken, sondern auch die Organisation der Datenverarbeitung. Das kann durchaus mehr als einen Tag dauern. Am besten beginnt man hier mit dem Interface, da es in manchen Fällen, so wie in unserem Beispiel, bestimmend werden kann,wenn man den Algorithmus strukturiert.
Zur Organisation des Dialogs des erzeugten Programms verwenden wir das Formular (ähnlich wie im Windows Anwendungsfenster (vgl. Schaubild in Abb. 1). Es enthält Zeilen, die wiederum aus Zellen und Zellen graphischer Objekte bestehen. Und damit beginnen wir schon jetzt, obwohl wir uns erst in der konzeptuellen Designphase befinden, die Programmstruktur und die Klassifikation der Objekte zu erkennen.
Abb. 1 Formular des Klassen-Constructors (Schaubild)
Trotz einer ausreichend großen Zahl an Reihen und Zellen (Feldern), bestehen sie dennoch nur aus zwei Arten von graphischen Objekten: OBJ_EDIT und OBJ_BUTTON . Sobald wir also das visuelle Erscheinungsbild, die Struktur und die vom Programm erzeugten Basisobjekte festgelegt haben, können wir davon ausgehen, dass der Designentwurf fertig ist und unser der nächsten Phase zuwenden.
2.2 Phase II: Design der Basisklasse
Es gibt drei solcher Klassen und es können (ggf.) später weitere hinzugefügt werden:
- Zellen-Klasse CCell;
- Reihen-Klasse CRow, besteht aus Zellen der Klasse CCell;
- Fenster-Klasse CWin, besteht aus Zeilen der Klasse CRow.
Wir können jetzt direkt zum Programmieren der Klassen weitergehen, doch... Moment, wir müssen zuvor noch eine sehr wichtige Aufgabe erledigen - der Datenaustausch zwischen Objekten der Klassen. Zu diesem Zweck enthält die MQL5-Sprache, neben den üblichen Variablen, einen neuen Typ - Struktur . Natürlich sehen wir in dieser Designphase noch nicht alle Verbindungen vollständig und es fällt uns auch schwer, sie zu berechnen. Und daher füllen wir im Verlauf des Projekts, Schritt für Schritt die Beschreibung der Klassen und Strukturen. Des Weiteren stehen die Prinzipien des OOP dem nicht nur nicht im Weg, sondern ganz im Gegenteil - sie unterstützen sogar die Technologie oder das Programmieren.
WinCell Struktur:
struct WinCell { color TextColor; // text color color BGColor; // background color color BGEditColor; // background color while editing ENUM_BASE_CORNER Corner; // anchor corner int H; // cell height int Corn; // displacement direction (1;-1) };
Strukturen, die keine Strings und Objekte dynamischer Arrays enthalten, nennt man einfache Strukturen. Die Variablen solcher Strukturen können ungehindert ineinander kopiert werden, selbst wenn die Strukturen unterschiedlich sind. Die somit erreichte Struktur ist genau dieser Art Ihre Effektivität bewerten wir später.
Basisklasse CCell:
//+------------------------------------------------------------------+ //| CCell base class | //+------------------------------------------------------------------+ class CCell { private: protected: bool on_event; // event processing flag ENUM_OBJECT type; // cell type public: WinCell Property; // cell property string name; // cell name //+---------------------------------------------------------------+ // Class constructor void CCell(); virtual // Draw method void Draw(string m_name, int m_xdelta, int m_ydelta, int m_bsize); virtual // Event processing method void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); };
Basisklasse CRow:
//+------------------------------------------------------------------+ //| CRow base class | //+------------------------------------------------------------------+ class CRow { protected: bool on_event; // event processing flag public: string name; // row name WinCell Property; // row property //+---------------------------------------------------------------+ // Class constructor void CRow(); virtual // Draw method void Draw(string m_name, int m_xdelta, int m_ydelta, int m_bsize); virtual // Event processing method void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); };
Basisklasse CWin:
//+------------------------------------------------------------------+ //| Base CWin class (WINDOW) | //+------------------------------------------------------------------+ class CWin { private: void SetXY(int m_corner); //Coordinates protected: bool on_event; // event processing flag public: string name; // window name int w_corner; // window corner int w_xdelta; // vertical delta int w_ydelta; // horizontal detla int w_xpos; // X coordinate int w_ypos; // Y coordinate int w_bsize; // Window width int w_hsize; // Window height int w_h_corner; // hide mode corner WinCell Property; // Property //--- CRowType1 STR1; // CRowType1 CRowType2 STR2; // CRowType2 CRowType3 STR3; // CRowType3 CRowType4 STR4; // CRowType4 CRowType5 STR5; // CRowType5 CRowType6 STR6; // CRowType6 //+---------------------------------------------------------------+ // Class constructor void CWin(); // Set window properties void SetWin(string m_name, int m_xdelta, int m_ydelta, int m_bsize, int m_corner); virtual // Draw window method void Draw(int &MMint[][3], string &MMstr[][3], int count); virtual // OnEventTick handler void OnEventTick(); virtual // OnChart event handler method void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); };
Erklärungen und Empfehlungen:
- Alle dieser Basisklassen (in unserem Projekt) enthalten Methoden zur Verarbeitung von Ereignissen. Sie sind für das Abfangen und Übertragen von Ereignissen an weiteren Punkten der Kette notwendig. Ohne einen Empfangs- und Sendemechanismus für Ereignisse, verliert das Programm (oder Modul) seine Interaktivität.
- Bei der Entwicklung einer Basisklasse sollte man darauf achten, nur ein Mindestmaß an Methoden zu verwenden. Danach kann man die "nachfolgenden" Klassen mit verschiedenen Erweiterungen dieser Klasse ausstatten, die die Funktionalität der erzeugten Objekte erhöhen.
- Bitte niemals einen direkten Aufruf an die internen Daten einer anderen Klasse verwenden!
2,3. Phase III: Arbeitsprojekt
In dieser Phase beginnen wir mit der schrittweisen Erzeugung des Programms. Beginnend mit dem unterstützenden Framework werden wir die funktionalen Komponenten des Programms steigern und es mit Inhalt füllen. Dabei kontrollieren wir die ganze Zeit über die Exaktheit der Arbeit, wenden eine Fehlersuche mit einem optimierten Code an und verfolgen alle sich zeigenden Fehler.
Halten wir hier kurz an und betrachten uns die Technologie der Erzeugung des Frameworks, das für fast jedes Programm so funktioniert. Seine Hauptvoraussetzung -es sollte sofort betriebsfähig sein (ohne Fehler erstellen und bei Ausführung sofort laufen). Darum haben sich die Sprach-Designer gekümmert und raten daher zum Einsatz des Expert Advisor Templates als Framework, dass vom MQL5-Assistenten generiert wird.
Betrachten wir als Beispiel unsere eigene Version dieses Templates:
1) Programm = Expert Advisor
//+------------------------------------------------------------------+ //| MasterWindows.mq5 | //| Copyright DC2008 | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "DC2008" #property link "http://www.mql5.com" #property version "1.00" //--- include files with classes #include <ClassMasterWindows.mqh> //--- Main module declaration CMasterWindows MasterWin; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Launch of the main module MasterWin.Run(); return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Deinitialization of the main module MasterWin.Deinit(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- call OnTick event handler of main module MasterWin.OnEventTick(); } //+------------------------------------------------------------------+ //| Expert Event function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- call OnChartEvent handler of main module MasterWin.OnEvent(id,lparam,dparam,sparam); }
Dies ist der abgeschlossene Code des Expert Advisors. Während des gesamten Projekts müssen keinerlei zusätzliche Veränderungen gemacht werden!
2) Hauptmodul = Klasse
Alle Haupt- und Hilfsmodule des Projekts beginnen mit ihrer Entwicklung hier. Dieses Verfahren vereinfacht das Programmieren komplexer multi-modularer Projekte und erleichtert die Suche nach möglichen Fehlern. Sie zu finden ist jedoch ziemlich schwer. Manchmal ist es in der Tat leichter und schneller, ein neues Projekt komplett zu schreiben als nach diesen schwer fassbaren "Fehlern" zu suchen.
//+------------------------------------------------------------------+ //| ClassMasterWindows.mqh | //| Copyright DC2008 | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "DC2008" #property link "http://www.mql5.com" //+------------------------------------------------------------------+ //| Main module: CMasterWindows class | //+------------------------------------------------------------------+ class CMasterWindows { protected: bool on_event; // event processing flag public: // Class constructor void CMasterWindows(); // Method of launching the main module (core algorithm) void Run(); // Deinitialization method void Deinit(); // OnTick event processing method void OnEventTick(); // OnChartEvent event processing method void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); };
Unten finden Sie eine grobe erste Beschreibung der Hauptmethoden der Klasse.
//+------------------------------------------------------------------+ //| CMasterWindows class constructor | //+------------------------------------------------------------------+ void CMasterWindows::CMasterWindows() { //--- class members initialization on_event=false; // disable events processing } //+------------------------------------------------------------------+ //| Метод запуска главного модуля (основной алгоритм) | //+------------------------------------------------------------------+ void CMasterWindows::Run() { //--- Main functional of the class: runs additional modules ObjectsDeleteAll(0,0,-1); Comment("MasterWindows for MQL5 © DC2008"); //--- on_event=true; // enable events processing } //+------------------------------------------------------------------+ //| Deinitialization method | //+------------------------------------------------------------------+ void CMasterWindows::Deinit() { //--- ObjectsDeleteAll(0,0,-1); Comment(""); } //+------------------------------------------------------------------+ //| OnTick() event processing method | //+------------------------------------------------------------------+ void CMasterWindows::OnEventTick() { if(on_event) // event processing is enabled { //--- } } //+------------------------------------------------------------------+ //| OnChartEvent() event processing method | //+------------------------------------------------------------------+ void CMasterWindows::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(on_event) // event processing is enabled { //--- } }
3) Die Library an Basis- und abgeleiteten Klassen
Die Library kann jede Anzahl abgeleiteter Klassen enthalten und daher sollte man sie am besten in separate Dateien gruppieren, die dann zusammen mit der Basisklasse (falls es eine gibt) mit eingeschlossen werden. So ist es nämlich leichter, die notwendigen Veränderungen und Ergänzungen hinzuzufügen und eben auch - nach Fehlern zu suchen.
Und schon haben wir das Programm-Framework. Testen wir es gleich und schauen mal, ob es korrekt funktioniert - also: erstellen und ausführen. Ist der Test erfolgreich verlaufen, können wir das Programm mit zusätzlichen Modulen füllen.
Beginnen wir hier also mit de Verknüpfung von abgeleiteten Klassen und fangen bei den Zellen an:
Namensklasse |
Bild |
---|---|
Klasse CCellText |
![]() |
Klasse CCellEdit |
![]() |
Klasse CCellButton |
![]() |
Klasse CCellButtonType |
![]() |
Tabelle 1 Library der Zellklassen
Betrachten wir uns die Erzeugung einer einzigen abgeleiteten Klasse des CCellButtonTyps im Detail. Diese Klasse erzeugt Tasten verschiedener Arten.
//+------------------------------------------------------------------+ //| CCellButtonType class | //+------------------------------------------------------------------+ class CCellButtonType:public CCell { public: ///Class constructor void CCellButtonType(); virtual ///Draw method void Draw(string m_name, int m_xdelta, int m_ydelta, int m_type); }; //+------------------------------------------------------------------+ //| CCellButtonType class constructor | //+------------------------------------------------------------------+ void CCellButtonType::CCellButtonType() { type=OBJ_BUTTON; on_event=false; //disable events processing } //+------------------------------------------------------------------+ //| CCellButtonType class Draw method | //+------------------------------------------------------------------+ void CCellButtonType::Draw(string m_name, int m_xdelta, int m_ydelta, int m_type) { //--- creating an object with specified name if(m_type<=0) m_type=0; name=m_name+".Button"+(string)m_type; if(ObjectCreate(0,name,type,0,0,0,0,0)==false) Print("Function ",__FUNCTION__," error ",GetLastError()); //--- object properties initializartion ObjectSetInteger(0,name,OBJPROP_COLOR,Property.TextColor); ObjectSetInteger(0,name,OBJPROP_BGCOLOR,Property.BGColor); ObjectSetInteger(0,name,OBJPROP_CORNER,Property.Corner); ObjectSetInteger(0,name,OBJPROP_XDISTANCE,m_xdelta); ObjectSetInteger(0,name,OBJPROP_YDISTANCE,m_ydelta); ObjectSetInteger(0,name,OBJPROP_XSIZE,Property.H); ObjectSetInteger(0,name,OBJPROP_YSIZE,Property.H); ObjectSetInteger(0,name,OBJPROP_SELECTABLE,0); if(m_type==0) // Hide button { ObjectSetString(0,name,OBJPROP_TEXT,CharToString(MIN_WIN)); ObjectSetString(0,name,OBJPROP_FONT,"Webdings"); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,12); } if(m_type==1) // Close button { ObjectSetString(0,name,OBJPROP_TEXT,CharToString(CLOSE_WIN)); ObjectSetString(0,name,OBJPROP_FONT,"Wingdings 2"); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,8); } if(m_type==2) // Return button { ObjectSetString(0,name,OBJPROP_TEXT,CharToString(MAX_WIN)); ObjectSetString(0,name,OBJPROP_FONT,"Webdings"); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,12); } if(m_type==3) // Plus button { ObjectSetString(0,name,OBJPROP_TEXT,"+"); ObjectSetString(0,name,OBJPROP_FONT,"Arial"); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,10); } if(m_type==4) // Minus button { ObjectSetString(0,name,OBJPROP_TEXT,"-"); ObjectSetString(0,name,OBJPROP_FONT,"Arial"); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,13); } if(m_type==5) // PageUp button { ObjectSetString(0,name,OBJPROP_TEXT,CharToString(PAGE_UP)); ObjectSetString(0,name,OBJPROP_FONT,"Wingdings 3"); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,8); } if(m_type==6) // PageDown button { ObjectSetString(0,name,OBJPROP_TEXT,CharToString(PAGE_DOWN)); ObjectSetString(0,name,OBJPROP_FONT,"Wingdings 3"); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,8); } if(m_type>6) // empty button { ObjectSetString(0,name,OBJPROP_TEXT,""); ObjectSetString(0,name,OBJPROP_FONT,"Arial"); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,13); } on_event=true; //enable events processing } //+------------------------------------------------------------------+
Notwendige Erklärungen:
- Im Klassen-Konstruktor führen wir eine Sperre bei der Verarbeitung von Ereignissen ein. Das ist nötig, um das Objekt auf seine Arbeit vorzubereiten und Störungen und Ablenkungen durch ankommende Ereignisse auszuschalten. Sind dann alle erforderlichen Arbeitsschritte abgeschlossen, werden wir diese Verarbeitung wieder gestatten, und das Objekt beginnt vollständig zu funktionieren.
- Die Draw-Methode arbeitet mit internen Daten und empfängt externe Daten. Daher sollten die Daten zunächst auf ihre Konformität geprüft und erst dann verarbeitet werden, damit es zu keinen unerwarteten Situationen kommt. Doch in unserem Beispiel hier lassen wir diesen Test erst einmal weg. Warum? Stellen Sie sich einfach vor, das Klassenobjekt ist ein Soldat, und Soldaten müssen nicht unbedingt die Pläne ihrer Vorgesetzten kennen. Sie müssen einzig und allein klar, rasch und strikt die Befehle ihrer Vorgesetzten ausführen, anstatt die erhaltenen Befehle zu analysieren und unabhängige Entscheidungen zu treffen. Daher müssen alle externen Daten entsprechend stimmen, bevor wir mit der Arbeit an dieser Klasse beginnen.
Jetzt müssen wir die ganze Library der Zellen testen. Und dazu fügen wir den folgenden Code in das Hauptmodul ein (nur kurzfristig, jetzt für unsere Testzwecke) und starten den Expert Advisor.
//--- include file with classes #include <ClassUnit.mqh> //+------------------------------------------------------------------+ //| Main module: CMasterWindows class | //+------------------------------------------------------------------+ class CMasterWindows { protected: bool on_event; // events processing flag WinCell Property; // cell property CCellText Text; CCellEdit Edit; CCellButton Button; CCellButtonType ButtonType; public: // Class constructor void CMasterWindows(); // Main module run method (core algorithm) void Run(); // Deinitialization method void Deinit(); // OnTick event processing method void OnEventTick(); // OnChart event processing method void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); }; //+------------------------------------------------------------------+ //| Main module run method (core algorithm) | //+------------------------------------------------------------------+ void CMasterWindows::Run() { //--- core algorithm - it launches additional modules ObjectsDeleteAll(0,0,-1); Comment("MasterWindows for MQL5 © DC2008"); //--- Text field Text.Draw("Text",50,50,150,"Text field"); //--- Edit field Edit.Draw("Edit",205,50,150,"default value",true); //--- LARGE BUTTON Button.Draw("Button",50,80,200,"LARGE BUTTON"); //--- Hide button ButtonType.Draw("type0",50,100,0); //--- Close button ButtonType.Draw("type1",70,100,1); //--- Return button ButtonType.Draw("type2",90,100,2); //--- Plus button ButtonType.Draw("type3",110,100,3); //--- Minus button ButtonType.Draw("type4",130,100,4); //--- None button ButtonType.Draw("type5",150,100,5); //--- None button ButtonType.Draw("type6",170,100,6); //--- None button ButtonType.Draw("type7",190,100,7); //--- on_event=true; // enable events processing }
Und wi dürfen nicht vergessen, die Ereignisse für die daraus resultierenden Klassen zu übertragen! Ist das noch nicht geschehen, kann der Umgang mit Projekten extrem schwer oder gar unmöglich werden.
//+------------------------------------------------------------------+ //| CMasterWindows class OnChart event processing method | //+------------------------------------------------------------------+ void CMasterWindows::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(on_event) // event processing is enabled { //--- process events for the cell class objects Text.OnEvent(id,lparam,dparam,sparam); Edit.OnEvent(id,lparam,dparam,sparam); Button.OnEvent(id,lparam,dparam,sparam); ButtonType.OnEvent(id,lparam,dparam,sparam); } }
Als Ergebnis sehen wir nun alle verfügbaren Optionen für Objekte der Library der Zellklassen.
Abb. 2 Library der Zellklassen
Testen wir jetzt die Arbeitseffizienz und die Reaktionen der Objekte auf Ereignisse:
- In das Bearbeitungsfeld geben wir jetzt anstatt "Standard" verschiedene Variablen ein. Variieren diese Werte, dann hat der Test geklappt.
- Wir drücken auf die Tasten, die in gedrückten Zustand bleiben, bis sie abermals gedrückt werden. Das ist jedoch noch keine zufriedenstellende Reaktion. Die Tasten müssen, nachdem sie einmal gedrückt wurden, automatisch in ihren Ursprungszustand zurückspringen. Und jetzt können wir die Stärke des OOP zeigen - die Möglichkeit der Vererbung. Unser Programm kann mehr als ein Dutzend Tasten verwenden, und dazu muss nicht jeder Taste extra die gewünschte Funktionalität hinzugefügt werden. Es reicht aus, die CCell Basisklasse zu ändern und wie von Zauberhand beginnen alle Objekte der abgeleiteten Klassen korrekt und reibungslos zu funktionieren!
//+------------------------------------------------------------------+ //| CCell class OnChart event processing method | //+------------------------------------------------------------------+ void CCell::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(on_event) // event processing is enabled { //--- button click event if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".Button",0)>0) { if(ObjectGetInteger(0,sparam,OBJPROP_STATE)==1) { //--- if button stays pressed Sleep(TIME_SLEEP); ObjectSetInteger(0,sparam,OBJPROP_STATE,0); ChartRedraw(); } } } }
Also ist die Library der Zellklassen getestet und mit dem Projekt verknüpft.
Als nächstes müssen wir eine Library an Linien hinzufügen:
Namensklasse |
Bild |
---|---|
Klasse CRowType1 (0) |
![]() |
Klasse CRowType1 (1) |
![]() |
Klasse CRowType1 (2) |
![]() |
Klasse CRowType1 (3) |
![]() |
Klasse CRowType2 |
![]() |
Klasse CRowType3 |
![]() |
Klasse CRowType4 |
![]() |
KlasseCRowType5 |
![]() |
KlasseCRowType6 |
![]() |
Tabelle 2 Library der Linienklassen
Wir testen sie ganz genauso. Nachdem alle Test abgeschlossen sind, kommen wir zur nächsten Phase.
2.4 Phase IV: Das Projekt bauen
Bis jetzt sind alle notwendigen Module erzeugt und getestet worden. Also machen wir uns nun an den Aufbau des Projekts. Als erstes erzeugen wir eine Kaskade mit der Form des Fensters aus Abb. 1 und füllen sie mit Funktionalität, d.h. programmierten Reaktionen aller Elemente und Module auf ankommende Ereignisse.
Dazu haben wir einen bereits fertigen Programmrahmen und das bereits vorbereitete Hauptmodul. Fangen wir mit ihm an. Das Modul ist eine der "Nachfolger"-Klassen der CWin Basisklasse, sodass sie alle öffentlichen Methoden und Felder der "Vorgänger"-Klasse per Vererbung übertragen bekommen hat. Deshalb müssen wir jetzt nur ein paar Methoden außer Kraft setzen, und schon ist unsere neue CMasterWindows Klasse fertig:
//--- include files with classes #include <ClassWin.mqh> #include <InitMasterWindows.mqh> #include <ClassMasterWindowsEXE.mqh> //+------------------------------------------------------------------+ //| CMasterWindows class | //+------------------------------------------------------------------+ class CMasterWindows:public CWin { protected: CMasterWindowsEXE WinEXE; // executable module public: void Run(); // Run method void Deinit(); // Deinitialization method virtual // OnChart event processing method void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); }; //+------------------------------------------------------------------+ //| CMasterWindows class deinitialization method | //+------------------------------------------------------------------+ void CMasterWindows::Deinit() { //---(delete all objects) ObjectsDeleteAll(0,0,-1); Comment(""); } //+------------------------------------------------------------------+ //| CMasterWindows class Run method | //+------------------------------------------------------------------+ void CMasterWindows::Run() { ObjectsDeleteAll(0,0,-1); Comment("MasterWindows for MQL5 © DC2008"); //--- creating designer window and launch executable object SetWin("CWin1",1,30,250,CORNER_RIGHT_UPPER); Draw(Mint,Mstr,21); WinEXE.Init("CWinNew",30,18); WinEXE.Run(); } //+------------------------------------------------------------------+ //| CMasterWindows class event processing method | //+------------------------------------------------------------------+ void CMasterWindows::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(on_event) // event processing is enabled { //--- Close button click in the main window if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,"CWin1",0)>=0 && StringFind(sparam,".Button1",0)>0) { ExpertRemove(); } //--- OnChart event processing for all objects STR1.OnEvent(id,lparam,dparam,sparam); STR2.OnEvent(id,lparam,dparam,sparam); STR3.OnEvent(id,lparam,dparam,sparam); STR4.OnEvent(id,lparam,dparam,sparam); STR5.OnEvent(id,lparam,dparam,sparam); STR6.OnEvent(id,lparam,dparam,sparam); WinEXE.OnEvent(id,lparam,dparam,sparam); } }
Das Hauptmodul an sich ist ziemlich klein, da es ja nichts anderes tun soll, als das Anwendungsfenster zu erzeugen. Danach überträgt es die Kontrolle auf das ausführbare WinEXE Modul, wo das wirklich Interessante stattfindet - die Reaktion auf ankommende Ereignisse.
Vorhin haben wir eine einfache WinCell Struktur zum Datenaustausch zwischen Objekten erzeugt, und jetzt sehen wir plötzlich, welche Vorteile uns diese Vorgehensweise gebracht hat. Der Kopierprozess aller Mitglieder dr Struktur ist sehr logisch und kompakt:
STR1.Property = Property; STR2.Property = Property; STR3.Property = Property; STR4.Property = Property; STR5.Property = Property; STR6.Property = Property;
Zu diesem Zeitpunkt könne wir die Detailbetrachtung des Klassendesigns beenden und zur visuellen Technologie ihres Baus weitergehen, was den Erzeugungsprozess neuer Klassen erheblich beschleunigt.
3. Visuelles Design der Klassen
Eine Klasse kann im MasterWindows Design für MQL5 viel schneller gebaut und erheblich leichter visualisiert werden:Abb. 3 Der Prozess des visuellen Designs
Alles was vom Entwickler jetzt verlangt wird, ist das Fensterformular zu zeichnen und zwar mit Hilfe des MasterWindows Formulars, und danach einfach die Reaktion auf das geplante Ereignis festzulegen. Der Code selbst wir automatisch erzeugt. Und das ist alles! Das Projekt ist abgeschlossen.
Abb. 4 zeigt ein Beispiel eines erzeugten Codes der CMasterWindows Klasse sowie des Expert Advisors (eine Datei wird im Order ...\MQL5\Files angelegt):
//****** Project (Expert Advisor): project1.mq5 //+------------------------------------------------------------------+ //| Code has been generated by MasterWindows Copyright DC2008 | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "DC2008" //--- include files with classes #include <ClassWin.mqh> int Mint[][3]= { {1,0,0}, {2,100,0}, {1,100,0}, {3,100,0}, {4,100,0}, {5,100,0}, {6,100,50}, {} }; string Mstr[][3]= { {"New window","",""}, {"NEW1","new1",""}, {"NEW2","new2",""}, {"NEW3","new3",""}, {"NEW4","new4",""}, {"NEW5","new5",""}, {"NEW6","new6",""}, {} }; //+------------------------------------------------------------------+ //| CMasterWindows class (main unit) | //+------------------------------------------------------------------+ class CMasterWindows:public CWin { private: long Y_hide; // Window shift vertical in hide mode long Y_obj; // Window shift vertical long H_obj; // Window shift horizontal public: bool on_hide; // HIDE mode flag CArrayString units; // Main window lines void CMasterWindows() {on_event=false; on_hide=false;} void Run(); // Run method void Hide(); // Hide method void Deinit() {ObjectsDeleteAll(0,0,-1); Comment("");} virtual void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); }; //+------------------------------------------------------------------+ //| CMasterWindows class Run method | //+------------------------------------------------------------------+ void CMasterWindows::Run() { ObjectsDeleteAll(0,0,-1); Comment("Code has been generated by MasterWindows for MQL5 © DC2008"); //--- creating main window and launch executable module SetWin("project1.Exp",50,100,250,CORNER_LEFT_UPPER); Draw(Mint,Mstr,7); } //+------------------------------------------------------------------+ //| CMasterWindows class Hide method | //+------------------------------------------------------------------+ void CMasterWindows::Hide() { Y_obj=w_ydelta; H_obj=Property.H; Y_hide=ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,0)-Y_obj-H_obj;; //--- if(on_hide==false) { int n_str=units.Total(); for(int i=0; i<n_str; i++) { long y_obj=ObjectGetInteger(0,units.At(i),OBJPROP_YDISTANCE); ObjectSetInteger(0,units.At(i),OBJPROP_YDISTANCE,(int)y_obj+(int)Y_hide); if(StringFind(units.At(i),".Button0",0)>0) ObjectSetString(0,units.At(i),OBJPROP_TEXT,CharToString(MAX_WIN)); } } else { int n_str=units.Total(); for(int i=0; i<n_str; i++) { long y_obj=ObjectGetInteger(0,units.At(i),OBJPROP_YDISTANCE); ObjectSetInteger(0,units.At(i),OBJPROP_YDISTANCE,(int)y_obj-(int)Y_hide); if(StringFind(units.At(i),".Button0",0)>0) ObjectSetString(0,units.At(i),OBJPROP_TEXT,CharToString(MIN_WIN)); } } //--- ChartRedraw(); on_hide=!on_hide; } //+------------------------------------------------------------------+ //| CMasterWindows class OnChartEvent event processing method | //+------------------------------------------------------------------+ void CMasterWindows::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(on_event // event handling is enabled && StringFind(sparam,"project1.Exp",0)>=0) { //--- call of OnChartEvent handlers STR1.OnEvent(id,lparam,dparam,sparam); STR2.OnEvent(id,lparam,dparam,sparam); STR3.OnEvent(id,lparam,dparam,sparam); STR4.OnEvent(id,lparam,dparam,sparam); STR5.OnEvent(id,lparam,dparam,sparam); STR6.OnEvent(id,lparam,dparam,sparam); //--- creating graphic object if(id==CHARTEVENT_OBJECT_CREATE) { if(StringFind(sparam,"project1.Exp",0)>=0) units.Add(sparam); } //--- edit [NEW1] in Edit STR1 if(id==CHARTEVENT_OBJECT_ENDEDIT && StringFind(sparam,".STR1",0)>0) { //--- event processing code } //--- edit [NEW3] : Plus button STR3 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".STR3",0)>0 && StringFind(sparam,".Button3",0)>0) { //--- event processing code } //--- edit [NEW3] : Minus button STR3 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".STR3",0)>0 && StringFind(sparam,".Button4",0)>0) { //--- event processing code } //--- edit [NEW4] : Plus button STR4 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".STR4",0)>0 && StringFind(sparam,".Button3",0)>0) { //--- event processing code } //--- edit [NEW4] : Minus button STR4 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".STR4",0)>0 && StringFind(sparam,".Button4",0)>0) { //--- event processing code } //--- edit [NEW4] : Up button STR4 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".STR4",0)>0 && StringFind(sparam,".Button5",0)>0) { //--- event processing code } //--- edit [NEW4] : Down button STR4 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".STR4",0)>0 && StringFind(sparam,".Button6",0)>0) { //--- event processing code } //--- [new5] button click STR5 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".STR5",0)>0 && StringFind(sparam,".Button",0)>0) { //--- event processing code } //--- [NEW6] button click STR6 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".STR6",0)>0 && StringFind(sparam,"(1)",0)>0) { //--- event processing code } //--- [new6] button click STR6 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".STR6",0)>0 && StringFind(sparam,"(2)",0)>0) { //--- event processing code } //--- button click [] STR6 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".STR6",0)>0 && StringFind(sparam,"(3)",0)>0) { //--- event processing code } //--- Close button click in the main window if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".Button1",0)>0) { ExpertRemove(); } //--- Hide button click in the main window if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".Button0",0)>0) { Hide(); } } } //--- Main module declaration CMasterWindows MasterWin; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- launch main module MasterWin.Run(); return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- main module deinitialization MasterWin.Deinit(); } //+------------------------------------------------------------------+ //| Expert Event function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- call OnChartEvent event handler MasterWin.OnEvent(id,lparam,dparam,sparam); }
Beim Starten sehen wir dann das folgende entwickelte Fester:
Abb. 4 Expert Advisor Projekt1 - Ergebnis des visuellen Designs von Klassen
Fazit
- Klassen müssen Schritt für Schritt in verschiedenen Phasen entwickelt werden. Durch Herunterbrechen der Gesamtaufgabe in Module, kann für jedes Modul eine separate Klasse erzeugt werden. Die Module werden ihrerseits wiederum in Kleinstmodule abgeleiteter oder Basisklassen heruntergebrochen.
- Darauf achten, die Basisklassen nicht mit eingebauten Methoden zu überfrachten - die Anzahl der Methoden sollte auf ein Minimum beschränkt sein.
- Das Design von Klassen mit Hilfe der visuellen Design-Umgebung ist sehr einfach, selbst für einen blutigen Anfänger, da der Code automatisch erzeugt wird.
Standort der Anhänge:
- masterwindows.mq5 - ...\MQL5\Experts\
- Rest im Ordner - ...\MQL5\Include\
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/53





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