Die Entwicklung von grafischen Oberflächen auf Basis von .Net Framework und C# (Teil 2): Weitere grafische Elemente

Vasiliy Sokolov | 9 Juli, 2019

Inhalt


Einführung

Seit Oktober 2018 unterstützt MetaTrader 5 Integration mit Net Famework Bibliotheken. Dieser Satz von Bibliotheken ist eigentlich viel mehr als ein Framework oder ein spezialisiertes System zur Ausführung einer bestimmten Reihe von Aufgaben, wie das Zeichnen von grafischen Fenstern oder die Implementierung einer Netzwerkinteraktion. Net Framework hat buchstäblich alles. Es ermöglicht die Entwicklung von Websites (Net Core, MVC), die Erstellung von Systemanwendungen mit einer einheitlichen professionellen Oberfläche (Windows Forms), den Aufbau komplexer verteilter Systeme mit Datenaustausch zwischen Knoten und die Arbeit mit Datenbanken (Entity Framework). Außerdem ist Net Framework eine riesige Gemeinschaft von Programmierern und Unternehmen mit Tausenden von verschiedenen Open-Source-Projekten. Wenn die Interaktion richtig organisiert ist, kann all dies heute in MQL verfügbar sein.

In diesem Artikel werden wir die Funktionen des GuiControllers, der im ersten Teil erstellt wurde, weiter entwickeln. Diese Funktionen zielt auf die Interaktion mit der grafischen Funktionsweise des Net Framework auf Basis der Technologie von Windows Forms ab. Derzeit sind viele Informationen über grafische MQL-Funktionen verfügbar. Es gibt viele verschiedene Bibliotheken, die mehr oder weniger dasselbe mit Hilfe von MQL tun. Deshalb möchte ich nicht, dass dieses Material von den Lesern als "eine weitere Bibliothek für die Arbeit mit Forms" wahrgenommen wird. Tatsächlich ist dieses Material nur ein Teil einer großen Serie von Artikeln, die die Interaktion mit dem Net Framework beschreiben und allmählich grenzenlose Funktionen dieser Softwareplattform aufdecken. Windows Forms ist nur einer der Bausteine innerhalb dieser Plattform, wenn auch ein sehr komfortabler und umfassender, wie jeder Teil der Net-Technologie. Das grafische Subsystem von Windows Forms ist ein guter Ausgangspunkt für die Erforschung dieses Frameworks. Nach gründlichem Studium kann es in anderen Interaktionen mit Net Framework angewendet werden. Außerdem ermöglicht es auch die Erstellung recht effizienter und vor allem einfach zu implementierender Handelspanels, EA-Konfiguration von Fenstern, erweiterten grafischen Indikatoren, Robotersteuerungssystemen und anderen Dingen im Zusammenhang mit der Interaktion zwischen Nutzern und der Handelsplattform.

Um all diese spannenden Funktionen zu implementieren, ist es jedoch notwendig, das Modul der Interaktion zwischen einem MQL-Programm und einer C#-Bibliothek deutlich zu verbessern. Wie Sie sich vielleicht erinnern, konnte das Modul GuiController im ersten Abschnitt nur mit wenigen Elementen von WinForms wie Schaltflächen (Button), Textbeschriftungen (Label), Textfeldern zur Eingabe des Textes (TextBox) und der vertikalen Bildlaufleiste interagieren. Trotz dieser spärlichen Unterstützung ist es uns gelungen, ein komplettes und ziemlich funktionales grafisches Panel zu erstellen:

Abb. 1. Das Handelspanel, das im ersten Teil des Artikels erstellt wurde

Trotz eines beeindruckenden Ergebnisses werden wir nicht damit aufhören und unsere Steuerung weiter verbessern. In diesem Artikel werden wir es mit zusätzlichen grafischen Elementen versehen, die es Nutzern ermöglichen, die meisten Arten von Formularen zu erstellen.


Organisieren der Tests von neuen Elementen

Um die Unterstützung für neue Elemente einzuführen, ist es notwendig, eine Art "Prüfstand" zu organisieren. Dies ermöglicht es uns, die Arbeit mit neuen Elementen zu verfeinern und mögliche Fehler bei der Einführung neuer Funktionen zu vermeiden. Unser "Prüfstand" soll aus der Steuerung, den Formularen mit den notwendigen grafischen Elementen und dem EA, das mit diesen Elementen umgehen soll, bestehen. Alle Formulare müssen sich in einer einzigen DemoForm.exe befinden. Innerhalb des EA werden wir einen nutzerdefinierten Parameter entwickeln, der angibt, welches grafische Formular von DemoForm.exe heruntergeladen werden soll:


 

Abb. 2. Auswahl des heruntergeladenen, nutzerdefinierten Formulars mit den erforderlichen Elementen

Der Test EA selbst ist recht einfach. Er soll aus zwei Teilen bestehen: der Download-Funktion (Standard OnInit-Initialisierungsfunktion) und der grafischen Ereignisbehandlungsfunktion (Ereignisschleife in der OnTimer-Funktion). Wie Sie sich vielleicht erinnern, wird die Arbeit im GuiController über den Aufruf statischer Methoden durchgeführt. Es gibt nur vier Hauptmethoden:

  1. Show Form - startet ein Formular aus einem bestimmten Build;
  2. HideForm - das Formular ausblenden;
  3. GetEvent - ruft ein Ereignis aus dem Formular ab;
  4. SendEvent - sendet ein Ereignis an das Formular.

In der Funktion OnInit laden wir das notwendige Fenster abhängig von einem ausgewählten Element herunter. Der Funktionsprototyp ist unten dargestellt:

int OnInit()
{
   switch(ElementType)
   {
      case WINFORM_TAB:
         GuiController::ShowForm("DemoForm.exe", "tab_form");
         break;
      case WINFORM_BUTTON:
         GuiController::ShowForm("DemoForm.exe", "button_form");
         break;
      ...
   }
   ...
}

In der Funktion OnTimer behandeln wir Ereignisse, die aus dem Formular kommen:

//+------------------------------------------------------------------+
//| Timer Funktion                                                   |
//+------------------------------------------------------------------+
void OnTimer()
{   
   //-- get new events by timer
   for(static int i = 0; i < GuiController::EventsTotal(); i++)
   {
      int id;
      string el_name;
      long lparam;
      double dparam;
      string sparam;
      GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
      ...
      if(id == TabIndexChange)
         printf("Selecet new tab. Index: " + (string)lparam + " Name: " + sparam);
      else if(id == ComboBoxChange)
         printf("ComboBox '" + el_name + "' was changed on " + sparam);
      ...
   }
}

Daher werden wir für jedes der grafischen Elemente ein kurzes Arbeitsbeispiel erstellen, das die Interaktion mit ihm veranschaulicht. Wir werden auch die unterstützten Ereignisse ausführlich beschreiben.


MessageBox (Nachrichtenfeld)

Ab der zweiten Version unterstützt die Steuerung Nachrichtenboxen. Dies ist ein Element für eine standardmäßige Nutzerinformation. Es bietet dem Nutzer auch mehrere Optionen und erhält eine Antwort in Form einer ausgewählten Option.

Um die Demonstration von Meldungsfenstern zu starten, wählen Sie beim Start des EA die Option 'Buttons and MessageBox' im Parameter Windows Forms Element Type. Nach dem Start des EA erscheint ein Formular, in dem Sie aufgefordert werden, eine der Optionen auszuwählen:

Abb. 3. Beispiel für ein Formular zum Anrufen von Nachrichtenboxen 

Dieses Formular, sowie alle nachfolgenden, ist eine Demonstration, daher ist es nicht mit einer Handelslogik ausgestattet. Nach dem Drücken einer der Tasten sendet der EA jedoch eine Warnmeldung, die eine Bestätigung der ausgewählten Aktionen anfordert. So wird beispielsweise das folgende Nachrichtenfenster angezeigt, wenn Sie auf SELL klicken:


Abb. 4. Trading EA fordert Bestätigung zur Eröffnung einer neuen Verkaufsposition an.

Nachdem ein Nutzer auf eine der Schaltflächen geklickt hat, wird das Klickereignis gespeichert und im GuiController-Ereignispuffer aufgezeichnet. Der EA überwacht den Ereignispuffer mit einer bestimmten Frequenz und beginnt mit der Behandlung, sobald es feststellt, dass ein neues Ereignis in den Puffer eingetragen wurde. Daher muss der EA das Ereignis "Button clicking" empfangen und darauf reagieren, indem es die 'MessageBox' oncoming event sendet.

for(static int i = 0; i < GuiController::EventsTotal(); i++)
   {
      int id;
      string el_name;
      long lparam;
      double dparam;
      string sparam;
      //-- Get a new event
      GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
      //-- Define its type - button clicking
      if(id == ClickOnElement)
      {
         //-- Display the names of the clicked button in the terminal console
         printf("You press '" + sparam + "' button");
         string msg;
         //-- Depending on the clicked button type, form the MessageBox message
         if(el_name != "btnCancelAll")
            msg = "Are you sure you want to open a new " + sparam + " position?";
         else
            msg = "Are you sure you want to close all positions?";
         //-- Send an ongoing event with the MessageBox display command
         GuiController::SendEvent("ButtonForm", MessageBox, LockControl, OKCancel, msg);
      }
      ...
   }

Lassen Sie uns die Signatur des Sendens eines Ereignisses analysieren:

GuiController::SendEvent("ButtonForm", MessageBox, LockControl, OKCancel, msg);

Das bedeutet, dass der EA darum bittet, das Nachrichtenfeld mit dem Haupttext in der Variablen "msg" mit den Schaltflächen OK und Cancel (OKCancel) anzuzeigen. Im ersten Teil des Artikels haben wir erwähnt, dass der erste Parameter der SendEvent-Methode den Namen des grafischen Elements des gesendeten Ereignisverbrauchers enthält. Dieses Feld funktioniert bei der MessageBox jedoch anders. Messageboxen sind nicht an ein bestimmtes Grafikfenster oder -element gebunden (obwohl Windows Frorms eine solche Bindung erlaubt). Daher erstellt GuiController das neue Nachrichtenfenster und benötigt keine Zieladresse. Im Allgemeinen sollte nach der Anzeige der Meldung das Fenster, auf das sie sich bezieht, blockiert werden. Es wäre in der Tat seltsam, wenn es bei der Anzeige einer Nachricht möglich wäre, wiederholt auf die Schaltfläche BUY oder SELL zu klicken, ohne die erscheinende MessageBox zu ignorieren. Daher steht der Name des ersten Parameters für dieses Ereignis im GuiController für das Element, das blockiert werden soll, bis der Nutzer auf eine der MessageBox-Schaltflächen klickt. Die Blockierfunktion eines beliebigen grafischen Elements ist optional. Sie wird durch die ganzzahlige lparam-Variable angegeben: 0 - keine Fenstersperre, 1 - Sperre ist vorhanden. Es ist jedoch viel bequemer, mit Konstanten zu arbeiten als mit Nullen und Einsen. Um dies zu erreichen, werden im GuiController über die BlockingControl-Aufzählung zwei Konstanten definiert:

Die Erste blockiert das Fenster, bevor ein Nutzer die Taste drückt, die Zweite tut nichts, damit das grafische Fenster für den Nutzer zugänglich bleibt.

Neben dem Text kann das Meldungsfenster auch verschiedene Tastenkombinationen enthalten. Durch Anklicken dieser Schaltflächen erklärt sich der Nutzer mit einer bestimmten Auswahl einverstanden. Die Schaltflächensets werden über die System-Enumeration System.Windows.Forms.MessageBoxButtons definiert. Die Elemente der Enumeration sind für MQL-Nutzer nicht verfügbar, da sie im externen Build definiert sind. Um die Arbeit von MQL-Programmierern, die mit GuiController arbeiten, zu erleichtern, haben wir die neue Enumeration - den Klon von System.Windows.Forms.MessageBoxButtons mit den gleichen Parametern - implementiert. Die Aufzählung ist in der IController.cs definiert:

//
// Summary:
//     Specifies constants defining which buttons to display on a System.Windows.Forms.MessageBox.
public enum MessageBoxButtons
{
    //
    // Summary:
    //     The message box contains an OK button.
    OK = 0,
    //
    // Summary:
    //     The message box contains OK and Cancel buttons.
    OKCancel = 1,
    //
    // Summary:
    //     The message box contains Abort, Retry, and Ignore buttons.
    AbortRetryIgnore = 2,
    //
    // Summary:
    //     The message box contains Yes, No, and Cancel buttons.
    YesNoCancel = 3,
    //
    // Summary:
    //     The message box contains Yes and No buttons.
    YesNo = 4,
    //
    // Summary:
    //     The message box contains Retry and Cancel buttons.
    RetryCancel = 5
}

Die Konstanten dieser Aufzählung stehen im MQL-Editor direkt zur Verfügung, z.B. über IntelliSens, was die Konfiguration der MessageBox sehr komfortabel macht. Wenn wir beispielsweise OKCancel durch YesNoCancel in SendEvent ersetzen, erhält das Dialogfenster einen weiteren Satz von Schaltflächen:

GuiController::SendEvent("ButtonForm", MessageBox, LockControl, YesNoCancel, msg);


Abb. 5. Die Standardkombination aus drei Tasten - Yes/No/Cancel (Ja/Nein/Abbrechen)

Neben Tasten-Sets unterstützt der GuiController auch die Konfiguration von Nachrichtensymbolen sowie den Kopftext des Fensters. Da die SendEvent-Methode eine feste Anzahl von Parametern hat, ist es sehr schwierig, alle Einstellungen durch sie zu übergeben, so dass eine alternative Lösung gefunden wurde. Eine Nachrichtentextzeile kann mit dem Symbol " |" in Abschnitte unterteilt werden. In diesem Fall ist jeder Abschnitt für einen bestimmten zusätzlichen Parameter verantwortlich. Die Anzahl der Abschnitte kann von einem (keine Separatoren) bis zu drei (zwei Separatoren) variieren. Lassen Sie uns einige Beispiele betrachten. Angenommen, Sie möchten eine einfache Nachricht ohne Symbol oder zusätzliche Beschriftung anzeigen. In diesem Fall lautet das Sendeformat der Nachricht wie folgt:

GuiController::SendEvent("ButtonForm", MessageBox, LockControl, OK, "This is a simple message");


Abb. 6. Die einfache Nachricht ohne Symbole und zusätzlichen Text im Fensternamen

Ein Symbol kann einer Nachricht hinzugefügt werden, indem eine spezielle Konstante im zusätzlichen Abschnitt verwendet wird. Angenommen, wir möchten eine Meldung mit dem Symbol Warnung anzeigen. Ersetzen Sie dazu das Textformat der Nachricht durch das folgende:

GuiController::SendEvent("ButtonForm", MessageBox, LockControl, OK, "Warning|Your action can be dangerous");

Abb. 7. Die Meldung mit dem Warnsymbol

Ein Symbol kann nicht nur über Schlüsselwörter, sondern auch über das Pseudonym-Icon gesetzt werden. Wenn wir anstelle von Warnung "?" eingeben, wirkt sich das wie folgt aus. Neben Warnung können wir Info-, Frage- und Fehlersymbole setzen. Nachfolgend finden Sie eine Tabelle mit Schlüsselwörtern und Pseudonym-Icons für die Symbole:

Icon Schlüsselwort Pseudonym-Icon

Warnung !

Fehler !!!

Info i

Frage  ?


Neben den Symbolen können wir auch einen Namen für ein Nachrichtenfenster festlegen. Trennen Sie dazu den Text durch "|" und geben Sie den Fensternamen ein. Hier ist ein Beispiel für eine vollständige Definition eines Fensters mit einer Fehlermeldung:

GuiController::SendEvent("ButtonForm", MessageBox, LockControl, OK, "!!!|The operation was cancelled|Critical Error");

Abb. 8. Ein benanntes Nachrichtenfenster mit dem Fehlersymbol

Die Steuerung verfügt über eine intelligente Zeilenerkennung. Wenn wir "!!!|The operation was cancelled" eingeben, wird das Symbol für kritische Fehler mit einer entsprechenden Meldung angezeigt. Wenn wir in der Zeile die beiden Abschnitte "The operation was cancelled|Critical Error" markieren, wird kein Symbol angezeigt, aber der Fenstername wird in Critical Error geändert.


TabControl

Registerkarten sind ein praktisches Werkzeug, um Elemente in Gruppen einzuteilen:

Abb. 9. Panel mit zwei Registerkarten

Das Steuerelement der Register unterstützt ein einzelnes TabIndexChange-Ereignis. Es wird angezeigt, dass ein Nutzer auf eine andere Registerkarte gewechselt ist. Der Test EA zeigt die Änderungen der Code-Tracking-Tabellierung auf dem Formular. Schauen wir uns den Code-Abschnitt an:

for(static int i = 0; i < GuiController::EventsTotal(); i++)
{
  int id;
  string el_name;
  long lparam;
  double dparam;
  string sparam;
  GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
  if(id == TabIndexChange)
     printf("Selecet new tab. Index: " + (string)lparam + " Name: " + sparam);
}

Das Ereignis TabIndexChange übergibt die beiden Parameter: lparam und sparam. Die Erste enthält den Index einer vom Nutzer gewählten Registerkarte. Die Zweite enthält den Namen einer ausgewählten Registerkarte. Wenn ein Nutzer beispielsweise die erste Registerkarte auswählt, schreibt der EA die folgende Meldung:

Selecet new tab. Index: 0 Name: tabPage1

Tabs sind äußerst nützliche grafische Elemente. Es ist nicht immer notwendig, die Tabellierung zu verfolgen. WindowsForm verlangt, dass alle Elemente eines einzelnen Formulars individuellen Namen haben. So sind zwei Elemente desselben Typs, die sich in verschiedenen Registerkarten befinden, eindeutig und sollten nach WindForms unterschiedlich benannt werden. Andererseits ist es in der Regel notwendig, das Drücken von Direktsteuerungen zu verfolgen. Eine Registerkarte, auf der sich ein solches Element befindet, ist nicht immer wichtig. Es ist jedoch manchmal notwendig, solche Ereignisse zu verfolgen, und deshalb stellt der GuiController eine für dieses Element ausreichende Interaktionsschnittstelle zur Verfügung.

Checkbox (Ankreuzkästchen)

Ein Ankreuzkästchen ist eines der Schlüsselelemente jeder grafischen Oberfläche. Trotz seiner Einfachheit wird es in einer Vielzahl von Schnittstellen verwendet, angefangen bei älteren Versionen von Windows bis hin zu Web- und mobilen Anwendungen. Es ermöglicht eine intuitive Anzeige jeder Option. Außerdem ist es möglich, Optionen anzuzeigen, die aus irgendeinem Grund nicht verfügbar sind, so dass Nutzer intuitiv Optionen auswählen können, die sich nicht widersprechen:

 

Abb. 10. Auswahl von Optionen über die Kombination von Kontrollkästchen

Ein Ankreuzkästchen hat drei Zustände: mit Häkchen (Checked), ohne Häkchen (Unchecked) und mit einem halben Häkchen (Indeterminate). Windows Forms verfügt über die Struktur System.Windows.Forms.CheckState, die diese Zustände beschreibt:

namespace System.Windows.Forms
{
    //
    // Summary:
    //     Specifies the state of a control, such as a check box, that can be checked, unchecked,
    //     or set to an indeterminate state.
    public enum CheckState
    {
        //
        // Summary:
        //     The control is unchecked.
        Unchecked = 0,
        //
        // Summary:
        //     The control is checked.
        Checked = 1,
        //
        // Summary:
        //     The control is indeterminate. An indeterminate control generally has a shaded
        //     appearance.
        Indeterminate = 2
    }
}

Jedes Mal, wenn ein Nutzer dieses Ankreuzkästchen anklickt, übergibt GuiController seinen Status über das Ereignis CheckBoxChange über die Variable lparam an die MQL EA. Seine Werte entsprechen einer den Optionen der Enumeration: 0 - Ohne Haken, 1 - Mit Haken, 2 - Unbestimmt.

Im Demo-Beispiel verfolgt der EA die Auswahl der Ankreuzkästchen 'Enable Trading On EURUSD' und 'Enable Trading On GBPUSD'. Sobald eine der Optionen verfügbar ist, schaltet sie auch ihre Unteroptionen 'Allow take profit' und 'Allow stop loss' frei. Umgekehrt, wenn ein Nutzer das Flag von einer der Hauptoptionen entfernt, werden seine Unteroptionen sofort gesperrt. Dies wird durch die beiden Ereignisse erreicht: ElementEnable und CheckBoxChange. Der folgende Code stellt den Algorithmus der Behandlung von Ankreuzkästchen durch den EA dar:

void OnTimer()
{   
   //-- get new events by timer
   for(static int i = 0; i < GuiController::EventsTotal(); i++)
   {
      int id;
      string el_name;
      long lparam;
      double dparam;
      string sparam;
      GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
      if(id == CheckBoxChange)
         ChangeEnableStopAndProfit(el_name, id, lparam, dparam, sparam);
   }
}

//+------------------------------------------------------------------+
//| Change enable stops and profit                                   |
//+------------------------------------------------------------------+
void ChangeEnableStopAndProfit(string el_name, int id, long lparam, double dparam, string sparam)
{
   int id_enable = ElementEnable;
   if(el_name == "EURUSDEnable")
   {
      GuiController::SendEvent("EURUSDProfit", id_enable, lparam, dparam, sparam);
      GuiController::SendEvent("EURUSDStop", id_enable, lparam, dparam, sparam);
   }
   else if(el_name == "GBPUSDEnable")
   {
      GuiController::SendEvent("GBPUSDProfit", id_enable, lparam, dparam, sparam);
      GuiController::SendEvent("GBPUSDStop", id_enable, lparam, dparam, sparam);
   }
}

Sobald der EA benachrichtigt wird, dass ein Nutzer eines der Kästchen angekreuzt hat, sendet er das laufende ElementEnable-Ereignis mit 'true' an den GuiController. Wenn ein Nutzer das Ankreuzkästchen hingegen deaktiviert, wird das ElementEnable gleich 'false' gesendet. Durch dieses Zusammenspiel von EA und Formular mit Hilfe verschiedener Ereignisse entsteht ein Interaktivitätseffekt: Das Formular beginnt, die Zugänglichkeit von Unterelementen in Abhängigkeit von einer Nutzerauswahl zu verändern, obwohl sich die Steuerungslogik selbst direkt im EA befindet.


Radio Button (Optionstaste)

Eine Optionstaste ist ein einfaches grafisches Element, mit dem der Nutzer einen notwendigen Punkt aus vordefinierten auswählen kann:

Abb. 11. Radio buttons

Wenn die Nutzer ihre Wahl ändern, erhält der EA das Änderungsereignis zweimal: von der nicht angekreuzten Schaltfläche und von der angekreuzten. Beide Ereignisse werden mit dem gleichen RadioButtonChange Identifikator verfolgt. Hier ist ein Beispiel für seine Verwendung:

for(static int i = 0; i < GuiController::EventsTotal(); i++)
{
  int id;
  string el_name;
  long lparam;
  double dparam;
  string sparam;
  GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
  else if(id == RadioButtonChange)
  {
      if(lparam == true)
         printf("Your have selected " + sparam);
      else
         printf("Your have deselected " + sparam);
  }
}

Der Parameter lparam enthält das Flag, das darüber informiert, was mit der Schaltfläche passiert ist: ob sie markiert (plaram = true) oder nicht markiert (lparam = false) wurde. Wenn Sie auf die Schaltflächen klicken, zeigt der EA ähnliche Meldungen im Terminal an:

Your have deselected Expert
Your have selected Indicator
Your have deselected Indicator
Your have selected Script
...


Combo Box (Kombinationsfeld)

Ein Kombinationsfeld ist eines der häufigsten Elemente. Neben der Ankreuzfeld wird sie sowohl in der Webentwicklung als auch in modernen mobilen Anwendungen eingesetzt. Es ist auch eines der am häufigsten verwendeten Elemente in Windows:


Abb. 12. Kombinationsfeld und verfügbare Menüpunkte

Das Kombinationsfeld wird in zwei Hauptmodi verwendet. Der erste ermöglicht es dem Nutzer, neben den bestehenden Werten auch neue Werte einzugeben:

Abb. 13. Auswahl eines Symbols mit der Möglichkeit, ein neues einzugeben. 

Der zweite Modus bietet dem Nutzer nur vordefinierte Menüpunkte, ohne die Möglichkeit, eigene auszuwählen (siehe Abb. 11). Es gibt auch den dritten Modus, der die Menüpunkte ausblendet, aber er wird selten verwendet, so dass wir uns nicht damit beschäftigen werden. 

Alle Anzeigemodi der ComboBox werden über die Eigenschaft DropDownStyle eingestellt. Diese Eigenschaft wird normalerweise einmalig bei der Entwicklung der grafischen Oberfläche gesetzt, daher hat der GuiController kein Ereignis, das es Ihnen erlaubt, den Typ des Kombinationsfelds zu ändern. Die Steuerung ermöglicht es jedoch, die Auswahl eines Elements aus dem Kombinationsfeld zu verfolgen und einen neuen Wert einzugeben. Somit unterstützt das Kombinationsfeld zwei Ereignisse: ihr eigenes ComboBoxChange und TextChange. Unser Beispielformular besteht aus den beiden Kombinationsfeld-Elementen. Das erste bietet die Auswahl der Plattform (MetaTrader 4/MetaTrader 5), während das zweite das Symbol auswählt. Das zweite Element ist standardmäßig blockiert. Sobald der Nutzer jedoch eine Plattform auswählt, kann er ein Handelssymbol auswählen. Nachfolgend finden Sie den Code, der die Funktionalität implementiert: 

for(static int i = 0; i < GuiController::EventsTotal(); i++)
{
  int id;
  string el_name;
  long lparam;
  double dparam;
  string sparam;
  GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
  if(id == ComboBoxChange)
  {
     if(el_name == "comboBox1")
        //-- Unblock the list of symbols as soon as the user selects the platform:
        GuiController::SendEvent("comboBox2", ElementEnable, 1, 0.0, "");
     printf("ComboBox '" + el_name + "' was changed on " + sparam);
  }
}

Wenn wir mit der Auswahl von Elementen eines Kombinationsfeldes beginnen, beginnt der Demo-EA, Parameter der nutzerdefinierten Auswahl im Terminal anzuzeigen:

ComboBox 'comboBox1' was changed on MetaTrader 5
ComboBox 'comboBox2' was changed on GBPUSD
ComboBox 'comboBox2' was changed on USDJPY
...


NumericUpDown (Fenster numerischer Listen)

Ein numerisches Listenfenster wird häufig in Analysesystemen, einschließlich Handelspanels, verwendet. Deshalb gehörte dieses Element zu den ersten, die in GuiController integriert wurden. Ein numerisches Listenfenster ermöglicht die Einstellung eines bestimmten Wertes, der den Eingangstyp steuert. Es können nur Zahlen eingegeben werden. Der Schritt der Wertänderung kann mit einem speziellen Mini-Scroll geändert werden. Die Stellenanzahl der Nummer ist ebenfalls konfigurierbar:

Abb. 14. Numerisches Listenfenster

Der GuiController unterstützt für diesen Elementtyp vier Ereignisse:

NumericUpDown interagiert mit Nutzern über ein einziges NumericChange-Ereignis. Wenn ein Nutzer den Zahlenwert in diesem Fenster ändert, erhält der EA über das Ereignis eine entsprechende Benachrichtigung. Dies ist die einzig mögliche Nutzerinteraktion. Der EA kann jedoch das Fenster mit den wichtigsten Parametern konfigurieren: Dezimalstellen, Änderungsschritt sowie maximale und minimale zulässige Werte. Alle diese Parameter hängen von der EA-Logik und der Art der Daten ab, mit denen sie arbeiten, daher ist es unmöglich, sie direkt in den Formulareinstellungen zu definieren. Sie sollten beim Programmstart definiert werden.

Der Test-EA enthält ein kleines Beispiel, das die Arbeit mit NumericUpDown veranschaulicht. Der Code für das Hochladen des Formulars aus Abb. 13 ist unten aufgeführt. 

//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
{
   if(ElementType != WINFORM_HIDE)
      EventSetMillisecondTimer(100);
   else
      EventSetMillisecondTimer(1000);
   switch(ElementType)
   {
      ...
      case WINFORM_NUMERIC:
      {
         GuiController::ShowForm(assembly, "NumericForm");
         double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
         double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
         double price_step = NormalizeDouble(SymbolInfoDouble(Symbol(), SYMBOL_TRADE_TICK_SIZE), Digits());
         long digits = (long)Digits();
         GuiController::SendEvent("NumericForm", TextChange, 0, 0.0, "NumericForm (" + Symbol() + ")");
         NumericSet("StopLoss", Digits(), ask, (double)LONG_MAX, 0.0, price_step);
         NumericSet("TakeProfit", Digits(), bid, (double)LONG_MAX, 0.0, price_step);
         break;
      }
      ...
   }
   return(INIT_SUCCEEDED);
}

Wie aus dem Code ersichtlich, erhält der EA während des Uploads Daten über das aktuelle Symbol: seine Ask- und Bid-Kurse, Dezimalstellen und Schrittweite der Kurse. Danach werden diese Parameter für NumericUpDown-Formularelemente mit der speziellen Hilfsfunktion NumericSet eingestellt. Lassen Sie uns einen Blick auf den Code werfen:

//+------------------------------------------------------------------+
//| Set NumericUpDownParameter                                       |
//| name - name of NumericUpDown element                             |
//| digits - digits of symbol                                        |
//| init - init double value                                         |
//| max - max value                                                  |
//| min - min value                                                  |
//| step - step of change                                            |
//+------------------------------------------------------------------+
void NumericSet(string name, long digits, double init, double max, double min, double step)
{
   int id_foramt_change = NumericFormatChange;
   int id_change = NumericChange;
   int id_max = NumericMaxChange;
   int id_min = NumericMinChange;
   long lparam = 0;
   double dparam = 0.0;
   string sparam = "";
   GuiController::SendEvent(name, id_max, lparam, max, sparam);
   GuiController::SendEvent(name, id_min, lparam, min, sparam);
   GuiController::SendEvent(name, id_change, lparam, init, sparam);
   GuiController::SendEvent(name, id_foramt_change, digits, step, sparam);
}

Der Code passt sich selber an. Abhängig vom Symbol, auf dem es gestartet wird, werden wir verschiedene Preisformate sehen:


Abb. 15. Individuelle Preisformate für jedes Symbol

DataTimePicker (Datumsauswahlfenster)

Dieses Element ist im Konzept ähnlich wie NumericUpDown mit dem einzigen Unterschied, dass es dem Nutzer erlaubt, Daten und nicht Zahlen sicher auszuwählen:

Abb. 16. Auswahl einer genauen Uhrzeit im DataTimePicker-Element

Die Interaktion mit dem DataTimePicker ist im Vergleich zum NumericUpDown-Element einfacher. Dies liegt daran, dass das Datumsformat im Gegensatz zum Zahlenformat, das von der aktuellen Handelsumgebung des EAs abhängt, mehr oder weniger universell ist. Es kann bei der Entwicklung des Formulars eingestellt und danach intakt gelassen werden. Daher unterstützt DataTimePicker ein einzelnes DateTimePickerChange Ereignis, das über den Parameter lparam ein genaues Datum übergibt und empfängt. Hier ist ein Beispiel für die Verwendung des Elements:

for(static int i = 0; i < GuiController::EventsTotal(); i++)
{
  int id;
  string el_name;
  long lparam;
  double dparam;
  string sparam;
  GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
  if(id == DateTimePickerChange)
     printf("User set new datetime value: " + TimeToString((datetime)lparam));
}

Wenn wir den Demo-EA starten und DateTimePicker als Demonstrationselement auswählen, erscheint ein Fenster ähnlich dem in Abb. 15 gezeigten. Wenn wir anfangen, Datum und Uhrzeit auf verschiedene Weise zu ändern, reagiert der EA auf diese Ereignisse und zeigt neue Datums- und Zeitwerte im Protokoll an:

User set a new datetime value: 2019.05.16 14:21
User set a new datetime value: 2019.05.16 10:21
User set a new datetime value: 2021.05.16 10:21

Die Interaktion mit dem Element ist jedoch etwas komplizierter, als es scheinen mag. MQL und C# haben unterschiedliche Zeitformate. MQL verfügt über ein vereinfachtes POSIX-Zeitformat mit einer Auflösung von 1 Sekunde und dem frühestmöglichen Datum von 1970.01.01, während C# ein fortschrittlicheres Zeitformat mit einer Auflösung von 100 Nanosekunden bietet. Um mit verschiedenen Systemen zu interagieren, müssen wir also den Zeitkonverter entwickeln, der ein Zeitformat in ein anderes umwandelt. Ein solcher Konverter wird im GuiController verwendet. Es ist als 'public' 'static' Klasse des MtConverters angelegt:

/// <summary>
/// System Converter MetaTrader - C# 
/// </summary>
public static class MtConverter
{
    /// <summary>
    /// Convert C# DateTime format to MQL (POSIX) DateTime format.
    /// </summary>
    /// <param name="date_time"></param>
    /// <returns></returns>
    public static long ToMqlDateTime(DateTime date_time)
    {
        DateTime tiks_1970 = new DateTime(1970, 01, 01);
        if (date_time < tiks_1970)
            return 0;
        TimeSpan time_delta = date_time - tiks_1970;
        return (long)Math.Floor(time_delta.TotalSeconds);
    }
    /// <summary>
    /// Convert MQL (Posix) time format to sharp DateTime value.
    /// </summary>
    /// <param name="mql_time">MQL datetime as tiks</param>
    /// <returns></returns>
    public static DateTime ToSharpDateTime(long mql_time)
    {
        DateTime tiks_1970 = new DateTime(1970, 01, 01);
        if (mql_time <= 0 || mql_time > int.MaxValue)
            return tiks_1970;
        TimeSpan time_delta = new TimeSpan(0, 0, (int)mql_time);
        DateTime sharp_time = tiks_1970 + time_delta;
        return sharp_time;
    }
}

Derzeit besteht die Klasse nur aus zwei Methoden: ToMqlDateTime konvertiert DateTime in MQL. Die zweite Methode macht genau das Gegenteil, indem sie den MQL-Zeitwert in eine C# DateTime-Struktur umwandelt. Da die Typen datetime (mql) und dateTime(C#) inkompatibel sind, erfolgt die Konvertierung über den allgemeinen Typ "long", der für alle Systeme gleich ist. Daher müssen wir beim Empfangen des DateTimePickerChange-Ereignisses lparam explizit in datetime konvertieren, um einen korrekten Zeitwert zu erhalten:

//-- Convert long value to datetime explicitly. It is completely safe
printf("User set new datetime value: " + TimeToString((datetime)lparam));

Wir können nicht nur neue Werte bekommen, sondern sie auch selbst in ähnlicher Weise setzen. So können wir beispielsweise mit dem folgenden Befehl die aktuelle Zeit des Terminals einstellen:

GuiController::SendEvent("DateTimePicker", DateTimePickerChange, ((long)TimeCurrent()), 0.0, "");


ElementHide und ElementEnable - Verstecken und Deaktivieren eines beliebigen Elements

Es gibt universelle Ereignisse, mit denen Sie jedes WinForms-Element steuern können. Dazu gehören die Ereignisse ElementHide und ElementEnable. Um das Element auszublenden, verwenden Sie ElementHide wie folgt:

GuiController::SendEvent("HideGroup", ElementHide, true, 0.0, "");

wobei HideGroup ein Name des auszublendenden Elements ist. Um das Element anzuzeigen, rufen Sie entsprechen das Folgende auf:

GuiController::SendEvent("HideGroup", ElementHide, false, 0.0, "");

Achten Sie auf den Namen des verwendeten Elements. Ein einzelnes Element in WindowsForm kann interne Elemente enthalten. Dies wird als verschachtelte Elemente bezeichnet. Eine solche Anordnung ermöglicht die Verwaltung aller Elemente auf Gruppenebene. Im Demo-Beispiel wird ein Textfeld verwendet, in das die Beschriftung "Label" hinein verschachtelt ist. Mit einer bestimmten Frequenz verschwindet die Box mit allen Elementen darin und erscheint dann wieder:

 

Abb. 17. Ausblenden eines beliebigen grafischen Elements vom EA

Außerdem kann jedes Element nicht verfügbar gemacht werden. Während es nicht verschwindet, wird die Arbeit mit ihm unmöglich. Dies ist eine nützliche Option, die es ermöglicht, komplexere und intuitivere Oberflächen zu gestalten. Wir haben die Arbeit mit diesem Ereignis bereits in der Beschreibung der Flags erwähnt. Das Element kann durch Senden des folgenden Ereignisses nicht verfügbar gemacht werden:

GuiController::SendEvent("element", ElementEnable, false, 0.0, "");

Das Element kann wieder aktiviert werden, indem man einfach das Flag 'false' durch 'true' ersetzt:

GuiController::SendEvent("element", ElementEnable, true, 0.0, "");


AddItem — Subelemente hinzufügen

Einige Elemente können andere Elemente enthalten. Der Inhalt dieser Subelemente ist oft vor dem Programmstart unbekannt. Angenommen, wir müssen eine Liste von Handelssymbolen anzeigen, damit der Nutzer das gewünschte Symbol auswählen kann. Für diese Zwecke ist es am sinnvollsten, die ComboBox zu verwenden:


Abb. 18. Liste der voreingestellten Symbole

Allerdings können Symbole bei der Erstellung eines grafischen Formulars nicht vorab eingegeben werden, da die Listen der verfügbaren Symbole von Broker zu Broker unterschiedlich sein können. Daher sollten die Inhalte dieses Typs dynamisch gebildet werden. Dazu wird der Befehl AddItem verwendet. Am einfachsten ist es, alle verfügbaren Symbole in MarketWatch aufzulisten und als Menüpunkte hinzuzufügen:

//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
{
   EventSetMillisecondTimer(100);
   GuiController::ShowForm(assembly, "SendOrderForm");
   for(int i = 0; i < SymbolsTotal(true); i++)
   {
      GuiController::SendEvent("SymbolComboBox", AddItem, 0, 0, SymbolName(i, true));
   }
   return(INIT_SUCCEEDED);
}

Wie man sehen kann, ist dieser Befehl sehr einfach zu bedienen. Obwohl der Befehl universell ist, können nicht alle Elemente Subelemente hinzufügen. Derzeit unterstützt GuiController dieses Ereignis nur für das Kombinationsfeld. Dies reicht jedoch aus, um effiziente grafische Oberflächen zu erstellen. Diese Liste kann in Zukunft erweitert werden.


Ausnahme — Ereignis des Empfangens von Ausnahmen

Es ist nicht immer möglich, ein Programm fehlerfrei zu schreiben. Darüber hinaus gibt es Situationen, in denen etwas schief gelaufen ist und das Programm nach einem unvorhergesehenen Szenario ausgeführt wurde. In diesen Fällen ist ein Feedback mit der Möglichkeit, Daten über das Geschehene zu erhalten, notwendig. Das Ausnahmeereignis wird für diese Art von Interaktionen bereitgestellt. Es wird vom GuiController selbst generiert und an den MQL-EA gesendet. Der GuiController erzeugt in zwei Fällen eine Fehlermeldung:

  1. Im Falle des Abfangens einer System-Exception;
  2. Falls ein vom EA gesendetes Ereignis ein dafür ausgewähltes Element nicht unterstützt oder das Ereignis selbst ungültige Werte hat.

Lassen Sie uns diese Optionen nacheinander analysieren. Um die erste Option zu veranschaulichen, kehren wir zu unserem Demo-Code zurück, nämlich zu der Option, die NumericUpDown-Elemente anzeigt. In dieser Startoption wird eine spezielle NumericSet-Funktion aufgerufen. Sie legt die Arbeitsbereiche der NumericUpDown-Elemente fest:

void NumericSet(string name, long digits, double init, double max, double min, double step)
{
   int id_foramt_change = NumericFormatChange;
   int id_change = NumericChange;
   int id_max = NumericMaxChange;
   int id_min = NumericMinChange;
   long lparam = 0;
   double dparam = 0.0;
   string sparam = "";
   // GuiController::SendEvent(name, id_max, lparam, max, sparam);
   GuiController::SendEvent(name, id_min, lparam, min, sparam);
   GuiController::SendEvent(name, id_change, lparam, init, sparam);
   GuiController::SendEvent(name, id_foramt_change, digits, step, sparam);
}

Diese Funktion wurde leicht geändert, so dass kein Maximalwert für das Element gesetzt ist (die entsprechende Zeichenkette darin wurde auskommentiert). Wenn Sie dieses Formular nach dem Kompilieren auf dem Gold-Chart ausführen, wird die Anzeige der aktuellen Kurse plötzlich beendet:

Abb. 19. Nullwerte im Preisfindungsformular

Was ist passiert? Zur Beantwortung dieser Frage wird das System der Ausnahmen verwendet. Jede Ausnahme kann über GuiController::GetEvent abgerufen werden, genau wie jede andere Nachricht:

//-- get new events by timer
for(static int i = 0; i < GuiController::EventsTotal(); i++)
{
  int id;
  string el_name;
  long lparam;
  double dparam;
  string sparam;
  GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
  ...
  if(id == Exception)
     printf("Unexpected exception: " + sparam);
  
}

Die Nachrichtenausgabe spricht bereits viel:

Unexpected exception: '1291,32' value is unacceptable for 'Value'. 'Value' should lie in the range from 'Minimum' to 'Maximum'.
Parameter name: Value
Unexpected exception: '1291,06' value is unacceptable for 'Value'. 'Value' should lie in the range from 'Minimum' to 'Maximum'.
Parameter name: Value

Tatsache ist, dass NumericUpDown standardmäßig einen Arbeitsbereich von 0 bis 100 hat. Dementsprechend führt die Festlegung des aktuellen Goldpreises (1292 $ pro Feinunze) zu dem Fehler. Um dies zu vermeiden, erweitern Sie den Bereich der zulässigen Werte programmgesteuert über das Ereignis NumericMaxChange. Dies ist es, was die auskommentierte Zeichenkette in der NumericSet-Funktion tut.

Die zweite Möglichkeit, eine Ausnahme aufzurufen, ist möglich, wenn ein falsches Ereignis gesendet wird. So ist es beispielsweise möglich, ein Ereignis an ein nicht vorhandenes Ziel zu senden:

GuiController::SendEvent("empty", ElementEnable, 0, 0.0, "");

Die Reaktion schaut dann so aus:

Unexpected exception: SendEvent: element with name 'empty' not find

Wir können auch versuchen, ein Ereignis zu senden, das das Element nicht unterstützt. Versuchen wir beispielsweise, den folgenden Text in das Eingabefeld Stop-Loss-Level (Elementtyp NumericUpDown) einzufügen:

GuiController::SendEvent("StopLoss", AddItem, 0, 0.0, "New Text");

Die Antwort wird sehr prägnant sein:

Unexpected exception: Element 'StopLos' doesn't support 'Add Item' event

Das System der Ausnahmen bietet eine unschätzbare Hilfe bei der Erstellung komplexer grafischer Anwendungen. Fehler in solchen Anwendungen sind unvermeidlich. Die Entwicklungsgeschwindigkeit und der Entwicklungskomfort hängen davon ab, wie schnell ein Programmierer sie erkennen kann.


Übersichtstabellen der verfügbaren grafischen Elemente und Ereignisse

Es ist sinnvoll, die unterstützten grafischen Elemente für die Arbeit mit dem GuiController zu systematisieren. Erstellen wir dazu eine Tabelle mit zusammenfassenden Informationen über diese Elemente und deren Verwendung in GuiController. Die Spalte "Sample usage" enthält einen kurzen Beispielcode, der veranschaulicht, wie dieses Element aus dem MQL-Programm verwendet werden kann. Der betreffende Code sollte als Teil des Gesamtmusters der Arbeit mit Elementen betrachtet werden. Zum Beispiel den Code des ersten Beispiels (MessageBox):

string msg = "!!!|The operation was cancelled|Critical Error";
GuiController::SendEvent("ButtonForm", MessageBox, LockControl, OK, msg);

sollte im folgenden Zusammenhang betrachtet werden:

void OnTimer
{
   //...
   //-- get new events by timer
   for(static int i = 0; i < GuiController::EventsTotal(); i++)
   {
     int id;
     string el_name;
     long lparam;
     double dparam;
     string sparam;
     GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
     if(id == MessageBox)
     {
        string msg = "!!!|The operation was cancelled|Critical Error";
        GuiController::SendEvent("ButtonForm", MessageBox, LockControl, OK, msg);
     }
  }
}

Das Muster wird in ähnlicher Weise auf andere Beispielverwendungen angewendet.

Grafisches Element Element- oder Ereignisname ID des Schlüsselereignisses Beispielhafte Verwendung

MessageBox (Nachrichtenfeld) MessageBox (Nachrichtenfeld)
string msg = "!!!|The operation was cancelled|Critical Error";
GuiController::SendEvent("ButtonForm", MessageBox, LockControl, OK, msg);



Registerkarte TabIndexChange
GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
printf("Selecet new tab. Index: " + (string)lparam + " Name: " + sparam);



Checkbox (Ankreuzkästchen) CheckBoxChange
GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
printf("Checked " + sparam + " " + lparam);


RadioButton RadioButtonChange
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            

GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); if(lparam == true)    printf("Your have selected " + sparam); else    printf("Your have deselected " + sparam);




ComboBox ComboBoxChange
GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
printf("ComboBox '" + el_name + "' was changed on " + sparam);


NumericUpDown NumericChange
NumericFormatChange
NumericMaxChange
NumericMinChange
GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
printf("Numeric '" + el_name + "' was changed, new value: " + DoubleToString(dparam, 4));



DateTimePicker DateTimePickerChange
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            

GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); printf("User set new datetime value: " + TimeToString((datetime)lparam));



 

Vertical Scroll  ScrollChange  
GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
printf("Vertical Scroll has new value: " + (string)lparam);

 

 TextBox TextChange   
GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
printf("new value entered: " + sparam);

 

 Tasten  ClickOnElement  
GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
printf("Button " + sparam + " is pressed");

  Label TextChange   
GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
printf("Label has new text: " + sparam);

Neben grafischen Elementen unterstützt der GuiController auch universelle Ereignisse für die Arbeit mit ihnen. Lassen Sie uns diese Ereignisse auch in Form einer Tabelle arrangieren:

Ereignis Beschreibung Beispielhafte Verwendung
ElementHide  Ein Element ein-/ausblenden
GuiController::SendEvent("HideGroup", ElementHide, true, 0.0, "");
ElementEnable  Ein Element de-/aktivieren
GuiController::SendEvent("HideGroup", ElementEnable, true, 0.0, "");
AddItem  Ein neues Element zu einem gewählten Element hinzufügen  
GuiController::ShowForm(assembly, "SendOrderForm");
for(int i = 0; i < SymbolsTotal(true); i++)
   GuiController::SendEvent("SymbolComboBox", AddItem, 0, 0, SymbolName(i, true));
Exception   Abfangen einer Ausnahme, die von innerhalb CLR aufgerufen wurde  
GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
printf("Unexpected exception: " + sparam);



Schlussfolgerung

Wir haben die wichtigsten grafischen Elemente von Windows Forms und Beispiele für die Interaktion mit ihnen analysiert. Diese Elemente sind wenige, aber sie stellen das Rückgrat jeder grafischen Anwendung dar. Obwohl sie keine Tabellen enthalten (ein weiteres äußerst wichtiges Element im Handel), können Sie sie dennoch bereits zur Erstellung funktionaler grafischer Anwendungen verwenden.

Die letzte Version von GuiController ist unten angehängt. Außerdem kann diese Version aus dem GitHub-Repository-System kopiert werden. Die Version der Bibliothek finden Sie hier: https://github.com/PublicMqlProjects/MtGuiController.git. Sie können auch das Projekt des Demonstrationsformulars kopieren. Es befindet sich unter https://github.com/PublicMqlProjects/GuiControllerElementsDemo.git. Lesen Sie den ersten Teil des Artikels, um herauszufinden, wie Sie die neueste Bibliotheksversion über das Versionskontrollsystem beziehen können. Die angehängte Datei enthält vollständige Quellcodes aller Projekte. Es gibt drei davon: den EA, das das Formular mit Elementen aufruft, den Formularsatz im DemoForm.exe Build und den kompilierten GuiController (Source\MQL5\Libraries\GuiController.dll) zusammen mit demjenigen in Form von Quellcodes (Source\Sharp\GuiController). Beachten Sie auch, dass der Demo-EA den absoluten Pfad zu einem gestarteten Formular benötigt. Auf Ihrem PC unterscheidet er sich von dem im EA-Parameter 'assemble'" angegebenen, also ersetzen Sie ihn bitte durch Ihren tatsächlichen Pfad.