Grafiken in der Bibliothek DoEasy (Teil 73): Das Formularobjekt eines grafischen Elements
Inhalt
- Konzept
- Verbesserung der Bibliothek der Klasse
- Die Klasse für den Mausstatus
- Die Klasse des Basisobjekts aller grafischen Elemente der Bibliothek
- Die Klasse des Formularobjekts der grafischen Elemente
- Test
- Was kommt als Nächstes?
Konzept
Moderne Programme, insbesondere analytische, sind in der Lage, riesige Datenmengen in ihren Berechnungen zu verwenden. Allerdings wäre es schwierig, etwas ohne Visualisierung zu verstehen. Außerdem wäre es ohne eine übersichtliche und bequeme Oberfläche eine ziemliche Herausforderung, das Programm in seinem vollen Umfang zu nutzen. Natürlich ist die Fähigkeit, mit Grafiken zu arbeiten, auch für unsere Bibliothek ein Muss. Deshalb beginnt der Artikel mit einem großen Abschnitt über die Arbeit mit grafischen Elementen.
Mein Ziel ist es, eine bequeme Funktionalität für die Erstellung einer breiten Palette verschiedener grafischer Objekte zu schaffen, allen Hauptklassen der Bibliothek die interaktive Arbeit mit Grafiken zu ermöglichen und grafische Objekte mit beliebig komplexen Komponentenhierarchien zu erstellen.
Beginnen wir mit grafischen Objekten, die auf der Standardbibliothek-Klasse CCanvas basieren. Die Klasse erlaubt es, auf einfache Weise beliebige eigene Bilder zu erstellen und diese als "Bausteine" für den Aufbau komplexerer Objekte zu verwenden. Es ist möglich, entweder fertige Bilder zu verwenden oder eigene Bilder auf eine erstellte Leinwand zu zeichnen. Persönlich finde ich die letztere Option spannender. Daher werde ich sie weitgehend für die Gestaltung meiner grafischen Objekte verwenden.
Die Hierarchie eines einzelnen Objekts sieht immer wie folgt aus:
- Das Basisobjekt aller grafischen Elemente der Bibliothek basiert auf der Klasse CObject. In ihm ist das Objekt der Klasse CCanvas deklariert. Außerdem enthält es alle für grafische Elemente üblichen Parameter, einschließlich Breite, Höhe, Diagrammkoordinaten, rechter und unterer Objektrand usw.
- Das Formularobjekt eines grafischen Elements — es stellt die Basis (Canvas) eines beliebigen grafischen Objekts dar. Es soll alle anderen Elemente des zusammengesetzten Objekts enthalten. Seine Parameter ermöglichen die Einstellung von Parametern für das gesamte grafische Objekt. Hier wird auch das Objekt der Klasse deklariert, das die Methoden der Arbeit mit dem Mausstatus (Cursor-Koordinaten und gedrückte Tasten) bereitstellt.
Die Hierarchie bildet einen harten Kern des Basiselements aller grafischen Objekte der Bibliothek, die auf der Klasse CCanvas basieren. Alle anderen erstellten Objekte basieren auf diesem Objekt und erben dessen grundlegende Eigenschaften.
Doch zunächst wollen wir die vorgefertigten Bibliotheksklassen etwas verbessern und neue Daten für die Objekte hinzufügen, die ich hier erstellen werde.
Verbesserung der Bibliothek der Klasse
Fügen wir den neuen Unterabschnitt der Canvas-Parameter in \MQL5\Include\DoEasy\Defines.mqh ein und fügen einen Makro-Ersatz mit seiner Aktualisierungsfrequenz hinzu:
//--- Parameters of the DOM snapshot series #define MBOOKSERIES_DEFAULT_DAYS_COUNT (1) // The default required number of days for DOM snapshots in the series #define MBOOKSERIES_MAX_DATA_TOTAL (200000) // Maximum number of stored DOM snapshots of a single symbol //--- Canvas parameters #define PAUSE_FOR_CANV_UPDATE (16) // Canvas update frequency //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+
Canvas-basierte Objekte sollten nicht öfter als alle 16 Millisekunden aktualisiert (neu gezeichnet) werden. Dadurch wird ein unnötiges Neuzeichnen des Bildschirms vermieden, das für das menschliche Auge unsichtbar ist, aber dennoch das System zusätzlich belastet. Bevor wir also ein Canvas-basiertes Objekt aktualisieren, müssen wir prüfen, wie viele Millisekunden seit seiner letzten Aktualisierung vergangen sind. Durch die Einstellung einer optimalen Latenz können wir eine akzeptable Darstellung des Bildschirms mit grafischen Objekten erreichen.
Ich werde die Klasse des Mausstatusobjekts erstellen, um den Status der Maustasten sowie der Umschalt- und Strg-Taste zu definieren. Dazu benötige ich zwei Enumerationen: die Liste der möglichen Zustände der Maustasten sowie der Umschalt- und Strg-Tasten und die Liste der möglichen Mauszustände relativ zum Formular. Fügen wir diese am Ende der Code-Datei hinzu:
//+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Data for working with mouse | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| The list of possible mouse buttons, Shift and Ctrl keys states | //+------------------------------------------------------------------+ enum ENUM_MOUSE_BUTT_KEY_STATE { MOUSE_BUTT_KEY_STATE_NONE = 0, // Nothing is clicked //--- Mouse buttons MOUSE_BUTT_KEY_STATE_LEFT = 1, // The left mouse button is clicked MOUSE_BUTT_KEY_STATE_RIGHT = 2, // The right mouse button is clicked MOUSE_BUTT_KEY_STATE_MIDDLE = 16, // The middle mouse button is clicked MOUSE_BUTT_KEY_STATE_WHELL = 128, // Scrolling the mouse wheel MOUSE_BUTT_KEY_STATE_X1 = 32, // The first additional mouse button is clicked MOUSE_BUTT_KEY_STATE_X2 = 64, // The second additional mouse button is clicked MOUSE_BUTT_KEY_STATE_LEFT_RIGHT = 3, // The left and right mouse buttons clicked //--- Keyboard keys MOUSE_BUTT_KEY_STATE_SHIFT = 4, // Shift is being held MOUSE_BUTT_KEY_STATE_CTRL = 8, // Ctrl is being held MOUSE_BUTT_KEY_STATE_CTRL_CHIFT = 12, // Ctrl and Shift are being held //--- Left mouse button combinations MOUSE_BUTT_KEY_STATE_LEFT_WHELL = 129, // The left mouse button is clicked and the wheel is being scrolled MOUSE_BUTT_KEY_STATE_LEFT_SHIFT = 5, // The left mouse button is clicked and Shift is being held MOUSE_BUTT_KEY_STATE_LEFT_CTRL = 9, // The left mouse button is clicked and Ctrl is being held MOUSE_BUTT_KEY_STATE_LEFT_CTRL_CHIFT = 13, // The left mouse button is clicked, Ctrl and Shift are being held //--- Right mouse button combinations MOUSE_BUTT_KEY_STATE_RIGHT_WHELL = 130, // The right mouse button is clicked and the wheel is being scrolled MOUSE_BUTT_KEY_STATE_RIGHT_SHIFT = 6, // The right mouse button is clicked and Shift is being held MOUSE_BUTT_KEY_STATE_RIGHT_CTRL = 10, // The right mouse button is clicked and Ctrl is being held MOUSE_BUTT_KEY_STATE_RIGHT_CTRL_CHIFT = 14, // The right mouse button is clicked, Ctrl and Shift are being held //--- Middle mouse button combinations MOUSE_BUTT_KEY_STATE_MIDDLE_WHEEL = 144, // The middle mouse button is clicked and the wheel is being scrolled MOUSE_BUTT_KEY_STATE_MIDDLE_SHIFT = 20, // The middle mouse button is clicked and Shift is being held MOUSE_BUTT_KEY_STATE_MIDDLE_CTRL = 24, // The middle mouse button is clicked and Ctrl is being held MOUSE_BUTT_KEY_STATE_MIDDLE_CTRL_CHIFT = 28, // The middle mouse button is clicked, Ctrl and Shift are being held }; //+------------------------------------------------------------------+ //| The list of possible mouse states relative to the form | //+------------------------------------------------------------------+ enum ENUM_MOUSE_FORM_STATE { MOUSE_FORM_STATE_NONE = 0, // Undefined state //--- Outside the form MOUSE_FORM_STATE_OUTSIDE_NOT_PRESSED, // The cursor is outside the form, the mouse buttons are not clicked MOUSE_FORM_STATE_OUTSIDE_PRESSED, // The cursor is outside the form, any mouse button is clicked MOUSE_FORM_STATE_OUTSIDE_WHEEL, // The cursor is outside the form, the mouse wheel is being scrolled //--- Within the form MOUSE_FORM_STATE_INSIDE_NOT_PRESSED, // The cursor is inside the form, the mouse buttons are not clicked MOUSE_FORM_STATE_INSIDE_PRESSED, // The cursor is inside the form, any mouse button is clicked MOUSE_FORM_STATE_INSIDE_WHEEL, // The cursor is inside the form, the mouse wheel is being scrolled //--- Within the window header area MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED, // The cursor is inside the active area, the mouse buttons are not clicked MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED, // The cursor is inside the active area, any mouse button is clicked MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL, // The cursor is inside the active area, the mouse wheel is being scrolled //--- Within the window scrolling area MOUSE_FORM_STATE_INSIDE_SCROLL_NOT_PRESSED, // The cursor is within the window scrolling area, the mouse buttons are not clicked MOUSE_FORM_STATE_INSIDE_SCROLL_PRESSED, // The cursor is within the window scrolling area, any mouse button is clicked MOUSE_FORM_STATE_INSIDE_SCROLL_WHEEL, // The cursor is within the window scrolling area, the mouse wheel is being scrolled }; //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Data for handling graphical elements | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| The list of graphical element types | //+------------------------------------------------------------------+ enum ENUM_GRAPH_ELEMENT_TYPE { GRAPH_ELEMENT_TYPE_FORM, // Simple form GRAPH_ELEMENT_TYPE_WINDOW, // Window }; //+------------------------------------------------------------------+
Die Liste mit den grafischen Elementtypen wurde hinzugefügt, um "sichere Plätze" für nachfolgende Klassen zu schaffen, die auf den hier erstellten basieren — die Listen werden in zukünftigen Artikeln ausgefüllt und verwendet.
Die Liste der möglichen Maus-, Umschalt- und Strg-Zustände enthält grundlegende Maus- und Tastenereignisse sowie einige ihrer Kombinationen, die wahrscheinlich am häufigsten benötigt werden.
Die Mauszustände sind eigentlich ein einfacher Satz von Bit-Flags, die in der Hilfe für das Ereignis CHARTEVENT_MOUSE_MOVE beschrieben sind.
In der Tabelle sind die Bits und die entsprechenden Maustasten-, Shift- und Strg-Zustände angegeben:
Bit | Beschreibung | Wert |
---|---|---|
0 | Status der linken Maustaste | 1 |
1 | Status der rechten Maustaste | 2 |
2 | UMSCHALT-Status | 4 |
3 | STRG-Status | 8 |
4 | Status der mittleren Maustaste | 16 |
5 | Status der ersten zusätzlichen Maustaste | 32 |
6 | Der Status der zweiten zusätzlichen Maustaste | 64 |
Die Tabelle ermöglicht es uns, Mausereignisse durch die Zahl zu definieren, die in der Variablen gesetzt ist, die Mausstatusbits speichert:
- Wenn nur die linke Taste angeklickt wird, ist die Variable gleich 1
- Wenn nur die linke Taste geklickt wird, dann 2
- Wenn beide Tasten geklickt werden, 1 + 2 = 3
- Wenn nur die linke Taste geklickt wird, während die Umschalttaste gehalten wird, 1 + 4 = 5
Aus diesem Grund werden die Werte in der Enumeration ENUM_MOUSE_BUTT_KEY_STATE genau entsprechend der angezeigten Berechnung der Variablen gesetzt, während die durch die Enumeration-Konstanten beschriebenen Flags aktiviert werden.
Die Enumeration ENUM_MOUSE_FORM_STATE dient zur Angabe der Mauszeigerposition relativ zum Formular bei angeklickten/freigegebenen Maustasten. Wir werden die Werte der Konstanten der Enumeration benötigen, um die relative Position des Mauszeigers, seiner Tasten und des Objekts, mit dem wir interagieren wollen, zu definieren.
Wir werden diese beiden Enumerationen in zwei Bytes der ushort-Variablen speichern können, um sofort das gesamte Bild dessen zu erfassen, was mit der Maus und ihrem Interaktionsobjekt geschieht. Die Tabelle enthält die gesamte Bitmap der Variablen:
Bit | Byte | Status | Wert |
---|---|---|---|
0 | 0 | linke Maustaste | 1 |
1 | 0 | rechte Maustaste | 2 |
2 | 0 | UMSCHALTTASTE | 4 |
3 | 0 | STRG-Taste | 8 |
4 | 0 | mittlere Maustaste | 16 |
5 | 0 | erste zusätzliche Maustaste | 32 |
6 | 0 | zweite zusätzliche Maustaste | 64 |
7 | 0 | Scrollen mit dem Rad | 128 |
8 (0) | 1 | Cursor innerhalb des Formulars | 256 |
9 (1) | 1 | Cursor innerhalb des aktiven Bereichs des Formulars | 512 |
10 (2) | 1 | Cursor innerhalb der Fensterkontrolle (minimieren/maximieren/schließen, etc.) | 1024 |
11 (3) | 1 | Cursor innerhalb des Fenster-Schiebebereichs | 2048 |
12 (4) | 1 | Cursor am linken Rand des Formulars | 4096 |
13 (5) | 1 | Cursor am unteren Rand des Formulars | 8192 |
14 (6) | 1 | Cursor am rechten Rand des Formulars | 16384 |
15 (7) | 1 | Cursor am oberen Rand des Formulars | 32768 |
Die Flags, die Mauszustände und Cursorpositionen relativ zum Formularobjekt und zum Fensterobjekt auf der Basis des Formulars anzeigen, sind für den Moment ausreichend.
Lassen Sie uns das Objekt der Pause-Klasse in \MQL5\Include\DoEasy\Services\Pause.mqh etwas verbessern.
Dessen Methode SetTimeBegin() setzt nicht nur eine neue Pausen-Countdown-Zeit, sondern auch die an die Methode übergebene Zeit, nämlich auf die Variable m_time_begin.
Dies ist nur erforderlich, um Daten an das Journal zu senden, und wird nicht benötigt, wenn wir nur eine Pause irgendwo innerhalb der Methode zählen wollen. Wir können einfach eine beliebige Zeit (einschließlich Null) an die Methode übergeben, aber ich habe mich entschieden, die Methodenüberladung ohne Angabe der Zeit zu implementieren:
//--- Set the new (1) countdown start time and (2) pause in milliseconds void SetTimeBegin(const ulong time) { this.m_time_begin=time; this.SetTimeBegin(); } void SetTimeBegin(void) { this.m_start=this.TickCount(); } void SetWaitingMSC(const ulong pause) { this.m_wait_msc=pause; }
Jetzt können wir die Objektklasse für den Mausstatus erstellen.
Die Klasse für den Mausstatus
Im Ordner \MQL5\Include\DoEasy\Services\ der Servicefunktionen und -klassen legen wir die Klasse CMouseState in MouseState.mqh an.
Wir deklarieren im privaten Bereich der Klasse Variablen zum Speichern von Objektparametern und zwei Methoden zum Setzen der Flags von Maustasten und Tastenzuständen. Wir lassen die Instruktionen bezüglich des Ortes der Bit-Flags in der ushort-Variablen zum Speichernder Bit-Flags der Mauszustände stehen:
//+------------------------------------------------------------------+ //| MouseState.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "DELib.mqh" //+------------------------------------------------------------------+ //| Mouse status class | //+------------------------------------------------------------------+ class CMouseState { private: int m_coord_x; // X coordinate int m_coord_y; // Y coordinate int m_delta_wheel; // Mouse wheel scroll value int m_window_num; // Subwindow index long m_chart_id; // Chart ID ushort m_state_flags; // Status flags //--- Set the status of mouse buttons, as well as of Shift and Ctrl keys void SetButtonKeyState(const int id,const long lparam,const double dparam,const ushort flags); //--- Set the mouse buttons and keys status flags void SetButtKeyFlags(const short flags); //--- Data location in the ushort value of the button status //----------------------------------------------------------------- // bit | byte | state | dec | //----------------------------------------------------------------- // 0 | 0 | left mouse button | 1 | //----------------------------------------------------------------- // 1 | 0 | right mouse button | 2 | //----------------------------------------------------------------- // 2 | 0 | SHIFT key | 4 | //----------------------------------------------------------------- // 3 | 0 | CTRL key | 8 | //----------------------------------------------------------------- // 4 | 0 | middle mouse button | 16 | //----------------------------------------------------------------- // 5 | 0 | 1 add. mouse button | 32 | //----------------------------------------------------------------- // 6 | 0 | 2 add. mouse button | 64 | //----------------------------------------------------------------- // 7 | 0 | scrolling the wheel | 128 | //----------------------------------------------------------------- //----------------------------------------------------------------- // 0 | 1 | cursor inside the form | 256 | //----------------------------------------------------------------- // 1 | 1 | cursor inside active area | 512 | //----------------------------------------------------------------- // 2 | 1 | cursor in the control area | 1024 | //----------------------------------------------------------------- // 3 | 1 | cursor in the scrolling area| 2048 | //----------------------------------------------------------------- // 4 | 1 | cursor at the left edge | 4096 | //----------------------------------------------------------------- // 5 | 1 | cursor at the bottom edge | 8192 | //----------------------------------------------------------------- // 6 | 1 | cursor at the right edge | 16384 | //----------------------------------------------------------------- // 7 | 1 | cursor at the top edge | 32768 | //----------------------------------------------------------------- public:
Im öffentlichen Abschnitt der Klasse legen wir die Methoden fest, die die Werte der Objekteigenschaften zurückgeben:
public: //--- Reset the states of all buttons and keys void ResetAll(void); //--- Set (1) the subwindow index and (2) the chart ID void SetWindowNum(const int wnd_num) { this.m_window_num=wnd_num; } void SetChartID(const long id) { this.m_chart_id=id; } //--- Return the variable with the mouse status flags ushort GetMouseFlags(void) { return this.m_state_flags; } //--- Return (1-2) the cursor coordinates, (3) scroll wheel value, (4) status of the mouse buttons and Shift/Ctrl keys int CoordX(void) const { return this.m_coord_x; } int CoordY(void) const { return this.m_coord_y; } int DeltaWheel(void) const { return this.m_delta_wheel; } ENUM_MOUSE_BUTT_KEY_STATE ButtKeyState(const int id,const long lparam,const double dparam,const string flags); //--- Return the flag of the clicked (1) left, (2) right, (3) middle, (4) first and (5) second additional mouse buttons bool IsPressedButtonLeft(void) const { return this.m_state_flags==1; } bool IsPressedButtonRight(void) const { return this.m_state_flags==2; } bool IsPressedButtonMiddle(void) const { return this.m_state_flags==16; } bool IsPressedButtonX1(void) const { return this.m_state_flags==32; } bool IsPressedButtonX2(void) const { return this.m_state_flags==64; } //--- Return the flag of the pressed (1) Shift, (2) Ctrl, (3) Shift+Ctrl key and the flag of scrolling the mouse wheel bool IsPressedKeyShift(void) const { return this.m_state_flags==4; } bool IsPressedKeyCtrl(void) const { return this.m_state_flags==8; } bool IsPressedKeyCtrlShift(void) const { return this.m_state_flags==12; } bool IsWheel(void) const { return this.m_state_flags==128; } //--- Return the flag indicating the status of the left mouse button and (1) the mouse wheel, (2) Shift, (3) Ctrl, (4) Ctrl+Shift bool IsPressedButtonLeftWheel(void) const { return this.m_state_flags==129; } bool IsPressedButtonLeftShift(void) const { return this.m_state_flags==5; } bool IsPressedButtonLeftCtrl(void) const { return this.m_state_flags==9; } bool IsPressedButtonLeftCtrlShift(void) const { return this.m_state_flags==13; } //--- Return the flag indicating the status of the right mouse button and (1) the mouse wheel, (2) Shift, (3) Ctrl, (4) Ctrl+Shift bool IsPressedButtonRightWheel(void) const { return this.m_state_flags==130; } bool IsPressedButtonRightShift(void) const { return this.m_state_flags==6; } bool IsPressedButtonRightCtrl(void) const { return this.m_state_flags==10; } bool IsPressedButtonRightCtrlShift(void) const { return this.m_state_flags==14; } //--- Return the flag indicating the status of the middle mouse button and (1) the mouse wheel, (2) Shift, (3) Ctrl, (4) Ctrl+Shift bool IsPressedButtonMiddleWheel(void) const { return this.m_state_flags==144; } bool IsPressedButtonMiddleShift(void) const { return this.m_state_flags==20; } bool IsPressedButtonMiddleCtrl(void) const { return this.m_state_flags==24; } bool IsPressedButtonMiddleCtrlShift(void)const { return this.m_state_flags==28; } //--- Constructor/destructor CMouseState(); ~CMouseState(); }; //+------------------------------------------------------------------+
Hier haben wir die Methoden, die die Klassenvariablen zurückgeben und einige Methoden, die die vordefinierten Zustände der Maustasten und Strg-/Umschalt-Tasten zurückgeben.
Im Klassenkonstruktor, wird die Methode zum Rücksetzen der Zustände von Tasten- und Tastenflags und zum Rücksetzen des Mausrad-Scrollwertes aufgerufen:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CMouseState::CMouseState() : m_delta_wheel(0),m_coord_x(0),m_coord_y(0),m_window_num(0) { this.ResetAll(); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CMouseState::~CMouseState() { } //+------------------------------------------------------------------+ //| Reset the states of all buttons and keys | //+------------------------------------------------------------------+ void CMouseState::ResetAll(void) { this.m_delta_wheel = 0; this.m_state_flags = 0; } //+------------------------------------------------------------------+
Die Methode zum Einstellen des Status der Maustasten sowie der Umschalt-/Strg-Tasten:
//+------------------------------------------------------------------+ //| Set the status of mouse buttons, as well as of Shift/Ctrl keys | //+------------------------------------------------------------------+ void CMouseState::SetButtonKeyState(const int id,const long lparam,const double dparam,const ushort flags) { //--- Reset the values of all mouse status bits this.ResetAll(); //--- If a chart or an object is left-clicked if(id==CHARTEVENT_CLICK || id==CHARTEVENT_OBJECT_CLICK) { //--- Write the appropriate chart coordinates and set the bit of 0 this.m_coord_x=(int)lparam; this.m_coord_y=(int)dparam; this.m_state_flags |=(0x0001); } //--- otherwise else { //--- in case of a mouse wheel scrolling if(id==CHARTEVENT_MOUSE_WHEEL) { //--- get the cursor coordinates and the total scroll value (the minimum of +120 or -120) this.m_coord_x=(int)(short)lparam; this.m_coord_y=(int)(short)(lparam>>16); this.m_delta_wheel=(int)dparam; //--- Call the method of setting flags indicating the states of the mouse buttons and Shift/Ctrl keys this.SetButtKeyFlags((short)(lparam>>32)); //--- and set the bit of 8 this.m_state_flags &=0xFF7F; this.m_state_flags |=(0x0001<<7); } //--- If this is a cursor movement, write its coordinates and //--- call the method of setting flags indicating the states of the mouse buttons and Shift/Ctrl keys if(id==CHARTEVENT_MOUSE_MOVE) { this.m_coord_x=(int)lparam; this.m_coord_y=(int)dparam; this.SetButtKeyFlags(flags); } } } //+------------------------------------------------------------------+
Hier wird geprüft, welches Chart-Ereignis behandelt wird.
Zuerst setzen wir alle Bits in der Variable zurück, die die Mausstatus-Bitflags speichert.
Als Nächstes setzen wir bei einem Mausklick auf ein Diagramm oder ein Objekt das Bit 0 in der Variablen, die die Bit-Flags speichert.
Im Falle eines Mausrad-Scroll-Ereignisses enthält der Integer-Parameter lparam Daten über die Cursor-Koordinaten, die Scroll-Größe und die Bit-Flags der Tastenzustände und der Strg-/Umschalttasten. Wir extrahieren alle Daten aus der lparam-Variable und schreiben sie in die Variablen, die die Cursor-Koordinaten speichern, und in die benutzerdefinierte Variable mit den Bit-Flags, so dass die im privaten Abschnitt der Klasse beschriebene Bit-Reihenfolge eingehalten wird. Als Nächstes setzen wir das Bit 8, das das Mausrad-Scroll-Ereignis anzeigt.
Wenn der Cursor über das Chart bewegt wird, schreiben wir die Cursor-Koordinaten in die Variablen und rufen die Methode zum Setzen der Bit-Flags auf, die die Maustasten und den Strg/Shift-Status anzeigen.
Die Methode zum Setzen der Flags, die die Maustasten- und Tastenzustände anzeigen:
//+------------------------------------------------------------------+ //| Set the mouse buttons and keys status flags | //+------------------------------------------------------------------+ void CMouseState::SetButtKeyFlags(const short flags) { //--- Left mouse button status if((flags & 0x0001)!=0) this.m_state_flags |=(0x0001<<0); //--- Right mouse button status if((flags & 0x0002)!=0) this.m_state_flags |=(0x0001<<1); //--- SHIFT status if((flags & 0x0004)!=0) this.m_state_flags |=(0x0001<<2); //--- CTRL status if((flags & 0x0008)!=0) this.m_state_flags |=(0x0001<<3); //--- Middle mouse button status if((flags & 0x0010)!=0) this.m_state_flags |=(0x0001<<4); //--- The first additional mouse button status if((flags & 0x0020)!=0) this.m_state_flags |=(0x0001<<5); //--- The second additional mouse button status if((flags & 0x0040)!=0) this.m_state_flags |=(0x0001<<6); } //+------------------------------------------------------------------+
Hier ist alles ganz einfach: Die Methode erhält die Variable mit den Statusflags der Maus. Darauf wenden wir die Bitmaske mit dem verifizierten Bit an, und zwar nacheinander. Der nach Anwendung der Bitmaske erhaltene Wert wird durch das bitweise "UND" nur dann wahr, wenn beide verifizierten Bits installiert sind (1). Wenn die Variable mit der angewendeten Maske ungleich Null ist (das verifizierte Bit ist installiert), schreiben wir das entsprechende Bit in die Variable zum Speichern von Bitflags.
Die Methode gibt die Zustände der Maustasten und Umschalt-/Strg-Tasten zurück:
//+------------------------------------------------------------------+ //| Return the mouse buttons and Shift/Ctrl keys states | //+------------------------------------------------------------------+ ENUM_MOUSE_BUTT_KEY_STATE CMouseState::ButtKeyState(const int id,const long lparam,const double dparam,const string flags) { this.SetButtonKeyState(id,lparam,dparam,(ushort)flags); return (ENUM_MOUSE_BUTT_KEY_STATE)this.m_state_flags; } //+------------------------------------------------------------------+
Hier rufen wir zunächst die Methode auf, die alle Statusflags der Maustasten und Strg-/Shift-Tasten prüft und setzt, und geben den Wert der m_state_flags Variablen als Wert der Enumeration ENUM_MOUSE_BUTT_KEY_STATE zurück. In der Enumeration entsprechen die Werte aller Konstanten dem Wert, der sich aus dem Satz der installierten Variablenbits ergibt. Wir geben also sofort einen der Werte der Enumeration zurück, der dann in den Klassen behandelt werden soll, die die Zustände der Maus, ihrer Tasten und Strg/Shift-Tasten benötigen. Die Methode wird aus der Funktion OnChartEvent() aufgerufen.
Die Klasse des Basisobjekts aller grafischen Elemente der Bibliothek
So wie die Hauptbibliotheksklassen von der Standardbibliothek-Basisklasse abstammen, sollten alle Klassen der grafischen Elementobjekte von ihr vererbt werden. Eine solche Vererbung erlaubt es, mit jedem grafischen Objekt wie mit einem Standard-MQL5-Objekt zu arbeiten. Es ist nämlich wichtig, dass wir mit den verschiedenen Typen von grafischen Objekten so umgehen können, wie wir es mit der Klasse CObject tun. Um dies zu erreichen, müssen wir ein neues Basisobjekt erstellen, das von CObject-Objekt abgeleitet wird und die gemeinsamen Variablen und Methoden für jedes (und jedes) grafische Objekt der Bibliothek enthält.
Im Folgenden sind die gemeinsamen Eigenschaften aufgeführt, die jedem grafischen Objekt eigen sind und im grafischen Basisobjekt vorhanden sind:
- Objektkoordinaten in einem Diagramm;
- Breite und Höhe eines Elements (Canvas), das andere Elemente von zusammengesetzten Objekten enthalten soll (mit denselben Eigenschaften, die allen Objekten gemeinsam sind);
- Koordinaten des rechten und unteren Canvas-Randes (der linke und obere Rand entsprechen den Koordinaten);
- verschiedene Objekt-IDs (Objekttyp, Name sowie Chart- und Subwindow-IDs);
- und einige zusätzliche Flags, die das Verhalten des Objekts bei der Interaktion mit ihm festlegen.
Die Klasse wird sehr einfach sein: private Variablen, geschützte Methoden zum Setzen und öffentliche Methoden zum Zurückgeben ihrer Werte.
Die Klasse soll von der Basisklasse der CObject-Standardbibliothek abgeleitet werden.
In \MQL5\Include\DoEasy\Objects\ legen wir den Ordner Graph\ an, der die Datei GBaseObj.mqh der Klasse CGBaseObj enthält:
//+------------------------------------------------------------------+ //| GBaseObj.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Services\DELib.mqh" //+------------------------------------------------------------------+ //| Class of the base object of the library graphical objects | //+------------------------------------------------------------------+ class CGBaseObj : public CObject { private: int m_type; // Object type string m_name_obj; // Object name long m_chart_id; // Chart ID int m_wnd_num; // Chart subwindow index int m_coord_x; // Canvas X coordinate int m_coord_y; // Canvas Y coordinate int m_width; // Width int m_height; // Height bool m_movable; // Object movability flag bool m_selectable; // Object selectability flag protected: //--- Set the values to class variables void SetNameObj(const string name) { this.m_name_obj=name; } void SetChartID(const long chart_id) { this.m_chart_id=chart_id; } void SetWindowNum(const int wnd_num) { this.m_wnd_num=wnd_num; } void SetCoordX(const int coord_x) { this.m_coord_x=coord_x; } void SetCoordY(const int coord_y) { this.m_coord_y=coord_y; } void SetWidth(const int width) { this.m_width=width; } void SetHeight(const int height) { this.m_height=height; } void SetMovable(const bool flag) { this.m_movable=flag; } void SetSelectable(const bool flag) { this.m_selectable=flag; } public: //--- Return the values of class variables string NameObj(void) const { return this.m_name_obj; } long ChartID(void) const { return this.m_chart_id; } int WindowNum(void) const { return this.m_wnd_num; } int CoordX(void) const { return this.m_coord_x; } int CoordY(void) const { return this.m_coord_y; } int Width(void) const { return this.m_width; } int Height(void) const { return this.m_height; } int RightEdge(void) const { return this.m_coord_x+this.m_width; } int BottomEdge(void) const { return this.m_coord_y+this.m_height; } bool Movable(void) const { return this.m_movable; } bool Selectable(void) const { return this.m_selectable; } //--- The virtual method returning the object type virtual int Type(void) const { return this.m_type; } //--- Constructor/destructor CGBaseObj(); ~CGBaseObj(); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CGBaseObj::CGBaseObj() : m_chart_id(::ChartID()), m_type(WRONG_VALUE), m_wnd_num(0), m_coord_x(0), m_coord_y(0), m_width(0), m_height(0), m_movable(false), m_selectable(false) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CGBaseObj::~CGBaseObj() { } //+------------------------------------------------------------------+
Die Basisobjektklasse CObject verfügt über die virtuelle Methode Type(), die den Objekttyp zurückgibt (zur Identifizierung von Objekten anhand ihres Typs). Die ursprüngliche Methode gibt immer Null zurück:
//--- method of identifying the object virtual int Type(void) const { return(0); }
Indem wir die Methode in den Nachfahren umdefinieren, geben wir den in der Variablen m_type eingestellten Objekttyp zurück.
Die grafischen Objekttypen werden in späteren Artikeln beim Anlegen von Objektklassen gesetzt. In der Zwischenzeit gibt die Methode -1 zurück (das ist der Wert, den wir in der Initialisierungsliste des Klassenkonstruktors setzen).
Die Klasse des Formularobjekts der grafischen Elemente
Das Formularobjekt ist die Basis für die Erstellung der übrigen Klassen der Bibliothek grafischer Elemente auf der Grundlage der Klasse CCanvas. Es soll als "Leinwand" (canvas) dienen, auf der die für verschiedene Objekte notwendigen Daten gezeichnet und andere Elemente angeordnet werden, die schließlich das fertige Objekt darstellen.
Vorerst wird es sich um ein einfaches Formular mit grundlegenden Parametern und Funktionen handeln (die Möglichkeit, den aktiven Bereich einzustellen, der für die Interaktion mit dem Cursor verwendet wird), sowie die Möglichkeit, ihn entlang des Diagramms zu bewegen.
Erstellen wir in \MQL5\Include\DoEasy\Objects\Graph\ die Datei Form.mqh der Klasse CForm.
Die Klasse sollte vom Basisobjekt aller grafischen Objekte der Bibliothek abgeleitet werden. Daher sollten die Dateien des grafischen Basisobjekts und die Maus-Eigenschaftsobjektklasse in sie aufgenommen werden:
//+------------------------------------------------------------------+ //| Form.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include <Canvas\Canvas.mqh> #include "GBaseObj.mqh" #include "..\..\Services\MouseState.mqh" //+------------------------------------------------------------------+ //| Class of the base object of the library graphical objects | //+------------------------------------------------------------------+ class CForm : public CGBaseObj { }
Wir deklarieren im geschützten Klassenabschnitt die Standard-Bibliotheksklasse CCanvas, CPause und CMouseState, die Variable zum Speichern der Mausstatuswerte, die Variable zum Speichern der Mausstatus-Bitflags und die Variablen zum Speichern der Objekteigenschaften:
//+------------------------------------------------------------------+ //| Class of the base object of the library graphical objects | //+------------------------------------------------------------------+ class CForm : public CGBaseObj { protected: CCanvas m_canvas; // CCanvas class object CPause m_pause; // Pause class object CMouseState m_mouse; // "Mouse status" class object ENUM_MOUSE_FORM_STATE m_mouse_state; // Mouse status relative to the form ushort m_mouse_state_flags; // Mouse status flags int m_act_area_left; // Left border of the active area (offset from the left border inward) int m_act_area_right; // Right border of the active area (offset from the right border inward) int m_act_area_top; // Upper border of the active area (offset from the upper border inward) int m_act_area_bottom; // Lower border of the active area (offset from the lower border inward) uchar m_opacity; // Opacity int m_shift_y; // Y coordinate shift for the subwindow private:
Wir deklarieren im privaten Bereich der Klasse Hilfsmethoden für den Klassenbetrieb:
private: //--- Set and return the flags indicating the states of mouse buttons and Shift/Ctrl keys ENUM_MOUSE_BUTT_KEY_STATE MouseButtonKeyState(const int id,const long lparam,const double dparam,const string sparam) { return this.m_mouse.ButtKeyState(id,lparam,dparam,sparam); } //--- Return the cursor position relative to the (1) form and (2) active area bool CursorInsideForm(const int x,const int y); bool CursorInsideActiveArea(const int x,const int y); public:
Die Methode MouseButtonKeyState() gibt den Wert zurück, der von der gleichnamigen Methode vom Mausstatus-Klassenobjekt zurückgegeben wird. Zwei weitere Methoden sind notwendig, um die Position des Mauszeigers relativ zum Formular und zum aktiven Bereich des Formulars zu definieren. Auf sie werde ich später noch etwas eingehen.
Im öffentlichen Teil der Klasse befinden sich die Methoden zum Erzeugen eines Formulars, zum Installieren des Formulars und zum Zurückgeben seiner Parameter:
public: //--- Create a form bool CreateForm(const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable=true, const bool selectable=true); //--- Return the pointer to a canvas object CCanvas *CanvasObj(void) { return &this.m_canvas; } //--- Set (1) the form update frequency, (2) the movability flag and (3) selectability flag for interaction void SetFrequency(const ulong value) { this.m_pause.SetWaitingMSC(value); } void SetMovable(const bool flag) { CGBaseObj::SetMovable(flag); } void SetSelectable(const bool flag) { CGBaseObj::SetSelectable(flag); } //--- Update the form coordinates (shift the form) bool Move(const int x,const int y,const bool redraw=false); //--- Return the mouse status relative to the form ENUM_MOUSE_FORM_STATE MouseFormState(const int id,const long lparam,const double dparam,const string sparam); //--- Return the flag of the clicked (1) left, (2) right, (3) middle, (4) first and (5) second additional mouse buttons bool IsPressedButtonLeftOnly(void) { return this.m_mouse.IsPressedButtonLeft(); } bool IsPressedButtonRightOnly(void) { return this.m_mouse.IsPressedButtonRight(); } bool IsPressedButtonMiddleOnly(void) { return this.m_mouse.IsPressedButtonMiddle(); } bool IsPressedButtonX1Only(void) { return this.m_mouse.IsPressedButtonX1(); } bool IsPressedButtonX2Only(void) { return this.m_mouse.IsPressedButtonX2(); } //--- Return the flag of the pressed (1) Shift and (2) Ctrl key bool IsPressedKeyShiftOnly(void) { return this.m_mouse.IsPressedKeyShift(); } bool IsPressedKeyCtrlOnly(void) { return this.m_mouse.IsPressedKeyCtrl(); } //--- Set the shift of the (1) left, (2) top, (3) right, (4) bottom edge of the active area relative to the form, //--- (5) all shifts of the active area edges relative to the form and (6) the form opacity void SetActiveAreaLeftShift(const int value) { this.m_act_area_left=fabs(value); } void SetActiveAreaRightShift(const int value) { this.m_act_area_right=fabs(value); } void SetActiveAreaTopShift(const int value) { this.m_act_area_top=fabs(value); } void SetActiveAreaBottomShift(const int value) { this.m_act_area_bottom=fabs(value); } void SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift); void SetOpacity(const uchar value) { this.m_opacity=value; } //--- Return the coordinate (1) of the left, (2) right, (3) top and (4) bottom edge of the form active area int ActiveAreaLeft(void) const { return this.CoordX()+this.m_act_area_left; } int ActiveAreaRight(void) const { return this.RightEdge()-this.m_act_area_right; } int ActiveAreaTop(void) const { return this.CoordY()+this.m_act_area_top; } int ActiveAreaBottom(void) const { return this.BottomEdge()-this.m_act_area_bottom; } //--- Return (1) the form opacity, coordinate (2) of the right and (3) bottom form edge uchar Opacity(void) const { return this.m_opacity; } int RightEdge(void) const { return CGBaseObj::RightEdge(); } int BottomEdge(void) const { return CGBaseObj::BottomEdge(); } //--- Event handler void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Constructors/Destructor CForm(const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable=true, const bool selectable=true); CForm(){;} ~CForm(); }; //+------------------------------------------------------------------+
Betrachten wir nun die Methoden der Klasse im Detail.
Im parametrischen Konstruktor erzeugen wir ein Formularobjekt mit den an den Konstruktor übergebenen Parametern:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CForm::CForm(const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable=true, const bool selectable=true) : m_act_area_bottom(0), m_act_area_left(0), m_act_area_right(0), m_act_area_top(0), m_mouse_state(0), m_mouse_state_flags(0) { if(this.CreateForm(chart_id,wnd_num,name,x,y,w,h,colour,opacity,movable,selectable)) { this.m_shift_y=(int)::ChartGetInteger(chart_id,CHART_WINDOW_YDISTANCE,wnd_num); this.SetWindowNum(wnd_num); this.m_pause.SetWaitingMSC(PAUSE_FOR_CANV_UPDATE); this.m_pause.SetTimeBegin(); this.m_mouse.SetChartID(chart_id); this.m_mouse.SetWindowNum(wnd_num); this.m_mouse.ResetAll(); this.m_mouse_state_flags=0; CGBaseObj::SetMovable(movable); CGBaseObj::SetSelectable(selectable); this.SetOpacity(opacity); } } //+------------------------------------------------------------------+
Hier initialisieren wir zunächst alle Variablen in der Initialisierungsliste des Konstruktors. Dann rufen wir die Methode zur Formularerstellung auf. Wenn das Formular erfolgreich erstellt wurde, setzen wir die an den Konstruktor übergebenen Parameter.
Im Destruktor der Klasse entfernen wir das erstellte grafische Objekt:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CForm::~CForm() { ::ObjectsDeleteAll(this.ChartID(),this.NameObj()); } //+------------------------------------------------------------------+
Die Methode, die das grafische Formularobjekt erzeugt:
//+------------------------------------------------------------------+ //| Create the graphical form object | //+------------------------------------------------------------------+ bool CForm::CreateForm(const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable=true, const bool selectable=true) { if(this.m_canvas.CreateBitmapLabel(chart_id,wnd_num,name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE)) { this.SetChartID(chart_id); this.SetWindowNum(wnd_num); this.SetNameObj(name); this.SetCoordX(x); this.SetCoordY(y); this.SetWidth(w); this.SetHeight(h); this.SetActiveAreaLeftShift(1); this.SetActiveAreaRightShift(1); this.SetActiveAreaTopShift(1); this.SetActiveAreaBottomShift(1); this.SetOpacity(opacity); this.SetMovable(movable); this.SetSelectable(selectable); this.m_canvas.Erase(::ColorToARGB(colour,this.Opacity())); this.m_canvas.Update(); return true; } return false; } //+------------------------------------------------------------------+
Wir verwenden die Methode CreateBitmapLabel() der Klasse CCanvas, um eine grafische Ressource mit der Chart-ID und dem Index des Unterfensters zu erstellen (der zweite Methode zum Formularaufruf). Wenn die grafische Ressource erfolgreich erstellt wurde, werden alle an die Methode übergebenen Parameter zugewiesen, es wird das Formular gefärbt, die Deckkraft mit der Methode Erase() gesetzt und die Änderungen auf dem Bildschirm mit der Methode Update() angezeigt.
Ich möchte den Begriff "Opazität" oder Farbdichte erklären. Die Klasse CCanvas erlaubt es, die Transparenz für Objekte einzustellen. 0 bedeutet eine komplette Transparenz der Farbe, während 255 sie komplett undurchsichtig macht. Hier scheint also alles invertiert zu sein. Daher habe ich mich für den Begriff "Opazität" entschieden, da die Werte von 0 - 255 genau einer Zunahme der Farbdichte von Null (komplett transparent) bis 255 (komplett undurchsichtig) entsprechen.
CForm-Klasse der Ereignisbehandlung:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CForm::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Get the status of mouse buttons, Shift/Ctrl keys and the state of a mouse relative to the form ENUM_MOUSE_BUTT_KEY_STATE mouse_state=this.m_mouse.ButtKeyState(id,lparam,dparam,sparam); this.m_mouse_state=this.MouseFormState(id,lparam,dparam-this.m_shift_y,sparam); //--- Initialize the difference between X and Y coordinates of the form and cursor static int diff_x=0; static int diff_y=0; //--- In case of a chart change event, recalculate the shift by Y for the subwindow if(id==CHARTEVENT_CHART_CHANGE) { this.m_shift_y=(int)::ChartGetInteger(this.ChartID(),CHART_WINDOW_YDISTANCE,this.WindowNum()); } //--- If the cursor is inside the form, disable chart scrolling, context menu and Crosshair tool if((this.m_mouse_state_flags & 0x0100)!=0) { ::ChartSetInteger(this.ChartID(),CHART_MOUSE_SCROLL,false); ::ChartSetInteger(this.ChartID(),CHART_CONTEXT_MENU,false); ::ChartSetInteger(this.ChartID(),CHART_CROSSHAIR_TOOL,false); } //--- Otherwise, if the cursor is outside the form, allow chart scrolling, context menu and Crosshair tool else { ::ChartSetInteger(this.ChartID(),CHART_MOUSE_SCROLL,true); ::ChartSetInteger(this.ChartID(),CHART_CONTEXT_MENU,true); ::ChartSetInteger(this.ChartID(),CHART_CROSSHAIR_TOOL,true); } //--- If the mouse movement event and the cursor are located in the form active area if(id==CHARTEVENT_MOUSE_MOVE && m_mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED) { //--- If only the left mouse button is being held and the form is moved, //--- set the new parameters of moving the form relative to the cursor if(IsPressedButtonLeftOnly() && this.Move(this.m_mouse.CoordX()-diff_x,this.m_mouse.CoordY()-diff_y)) { diff_x=this.m_mouse.CoordX()-this.CoordX(); diff_y=this.m_mouse.CoordY()-this.CoordY(); } } //--- In any other cases, set the parameters of shifting the form relative to the cursor else { diff_x=this.m_mouse.CoordX()-this.CoordX(); diff_y=this.m_mouse.CoordY()-this.CoordY(); } //--- Test display of mouse states on the chart Comment(EnumToString(mouse_state),"\n",EnumToString(this.m_mouse_state)); } //+------------------------------------------------------------------+
Die gesamte Logik wird in den Kommentaren erklärt. Die Methode sollte von der standardmäßigen Ereignisbehandlung durch OnChartEvent() des Programms aufgerufen werden und sie hat genau die gleichen Parameter.
Lassen Sie mich die spezielle Berechnung erklären, die an die Methode MouseFormState() übergeben wird. Wenn sich das Formular im Hauptdiagrammfenster befindet, ist die m_shift_y Variable gleich Null und der Ausdruck dparam-this.m_shift_y liefert die genaue Y-Cursorkoordinate. Wenn sich das Formular jedoch im Unterfenster des Charts befindet, ist die Verschiebung in der m_shift_y Variablen größer als Null, um die Y-Cursorkoordinate an die Koordinaten des Unterfensters anzupassen. Dementsprechend müssen wir auch die Y-Koordinate mit der in m_shift_y eingestellten Verschiebung an die Methoden zur Berechnung der Cursorkoordinaten übergeben. Andernfalls zeigen die Objektkoordinaten um die Anzahl der Pixel der in der Variablen angegebenen Verschiebung höher, als sie tatsächlich sind.
Die Methode, die die Cursorposition relativ zum Formular zurückgibt:
//+------------------------------------------------------------------+ //| Return the cursor position relative to the form | //+------------------------------------------------------------------+ bool CForm::CursorInsideForm(const int x,const int y) { return(x>=this.CoordX() && x<this.RightEdge() && y>=this.CoordY() && y<=this.BottomEdge()); } //+------------------------------------------------------------------+
Die Methode erhält die X- und Y-Koordinaten des Cursors.
Wenn
- (die Cursor-X-Koordinate größer oder gleich der X-Koordinate des Formulars ist und die Cursor-X-Koordinate kleiner oder gleich der Koordinate des rechten Formularrandes ist) und
- (die Cursor-Y-Koordinate größer oder gleich der Y-Koordinate des Formulars, und die Cursor-Y-Koordinate kleiner oder gleich der Koordinate des unteren Randes des Formulars ist),
wird true zurückgegeben — der Cursor befindet sich innerhalb des Formularobjekts.
Die Methode gibt die Cursorposition relativ zum aktiven Bereich des Formulars zurück:
//+------------------------------------------------------------------+ //| Return the cursor position relative to the form active area | //+------------------------------------------------------------------+ bool CForm::CursorInsideActiveArea(const int x,const int y) { return(x>=this.ActiveAreaLeft() && x<this.ActiveAreaRight() && y>=this.ActiveAreaTop() && y<=this.ActiveAreaBottom()); } //+------------------------------------------------------------------+
Die Methode erhält die X- und Y-Koordinaten des Cursors.
Wenn
- (die Cursor-X-Koordinate größer oder gleich der X-Koordinate des formularaktiven Bereichs und die Cursor-X-Koordinate kleiner oder gleich der Koordinate des rechten Randes des formularaktiven Bereichs ist) und
- (die Cursor-Y-Koordinate größer oder gleich der Y-Koordinate des aktiven Formularbereichs und die Cursor-Y-Koordinate kleiner oder gleich der Koordinate des unteren Rands des aktiven Formularbereichs ist),
wird true zurückgegeben — der Cursor befindet sich innerhalb des aktiven Bereichs des Formularobjekts.
Die Methode gibt den Mausstatus relativ zum Formular zurück:
//+------------------------------------------------------------------+ //| Return the mouse status relative to the form | //+------------------------------------------------------------------+ ENUM_MOUSE_FORM_STATE CForm::MouseFormState(const int id,const long lparam,const double dparam,const string sparam) { //--- Get the mouse status relative to the form, as well as the states of mouse buttons and Shift/Ctrl keys ENUM_MOUSE_FORM_STATE form_state=MOUSE_FORM_STATE_NONE; ENUM_MOUSE_BUTT_KEY_STATE state=this.MouseButtonKeyState(id,lparam,dparam,sparam); //--- Get the mouse status flags from the CMouseState class object and save them in the variable this.m_mouse_state_flags=this.m_mouse.GetMouseFlags(); //--- If the cursor is inside the form if(this.CursorInsideForm(m_mouse.CoordX(),m_mouse.CoordY())) { //--- Set bit 8 responsible for the "cursor inside the form" flag this.m_mouse_state_flags |= (0x0001<<8); //--- If the cursor is inside the active area, set bit 9 "cursor inside the active area" if(CursorInsideActiveArea(m_mouse.CoordX(),m_mouse.CoordY())) this.m_mouse_state_flags |= (0x0001<<9); //--- otherwise, release the bit "cursor inside the active area" else this.m_mouse_state_flags &=0xFDFF; //--- If one of the mouse buttons is clicked, check the cursor location in the active area and //--- return the appropriate value of the pressed key (in the active area or the form area) if((this.m_mouse_state_flags & 0x0001)!=0 || (this.m_mouse_state_flags & 0x0002)!=0 || (this.m_mouse_state_flags & 0x0010)!=0) form_state=((m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : MOUSE_FORM_STATE_INSIDE_PRESSED); //--- otherwise, check the cursor location in the active area and //--- return the appropriate value of the unpressed key (in the active area or the form area) else form_state=((m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : MOUSE_FORM_STATE_INSIDE_NOT_PRESSED); } return form_state; } //+------------------------------------------------------------------+
Jeder Codezeile wird durch den jew. Kommentar erklärt. Kurz gesagt, wir holen uns einen vorgefertigten Mausstatus aus dem Mausstatus-Klassenobjekt und schreiben ihn in die Variable m_mouse_state_flags. Als Nächstes ergänzen wir in Abhängigkeit von der Cursorposition relativ zum Formular die Mausstatus-Bitflags mit neuen Daten und geben den Mausstatus im Format der Enumeration ENUM_MOUSE_FORM_STATE zurück, das wir oben am Anfang des Artikels betrachtet haben.
Die Methode aktualisiert die Formularkoordinaten (Verschieben des Formulars auf dem Chart):
//+------------------------------------------------------------------+ //| Update the form coordinates | //+------------------------------------------------------------------+ bool CForm::Move(const int x,const int y,const bool redraw=false) { //--- If the form is not movable, leave if(!this.Movable()) return false; //--- If new values are successfully set into graphical object properties if(::ObjectSetInteger(this.ChartID(),this.NameObj(),OBJPROP_XDISTANCE,x) && ::ObjectSetInteger(this.ChartID(),this.NameObj(),OBJPROP_YDISTANCE,y)) { //--- set the new values of X and Y coordinate properties this.SetCoordX(x); this.SetCoordY(y); //--- If the update flag is activated, redraw the chart. if(redraw) ::ChartRedraw(this.ChartID()); //--- Return 'true' return true; } //--- Something is wrong... return false; } //+------------------------------------------------------------------+
Die Methode erhält die Koordinaten, auf die wir das Formularobjekt verschieben möchten. Wenn die neuen Koordinatenparameter erfolgreich auf das grafische Formularobjekt gesetzt wurden, schreiben wir diese Koordinaten in die Objekteigenschaften und zeichnen das Chart nur dann neu, wenn das Flag zum Neuzeichnen (das ebenfalls an die Methode übergeben wird) aktiviert ist. Das Neuzeichnen durch den Flag-Wert ist notwendig, um ein mehrfaches Neuzeichnen des Charts zu vermeiden, falls das grafische Formularobjekt aus vielen Formularen besteht. In diesem Fall müssen wir zunächst alle Formulare eines Objekts verschieben. Nachdem jedes Formular neue Koordinaten erhalten hat, wird das Chart einmal aktualisiert.
Die Methode setzt alle Verschiebungen der aktiven Bereiche relativ zum Formular:
//+------------------------------------------------------------------+ //| Set all shifts of the active area relative to the form | //+------------------------------------------------------------------+ void CForm::SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift) { this.SetActiveAreaLeftShift(left_shift); this.SetActiveAreaBottomShift(bottom_shift); this.SetActiveAreaRightShift(right_shift); this.SetActiveAreaTopShift(top_shift); } //+------------------------------------------------------------------+
Wir haben zwar eigene Methoden zum Setzen der Grenzen des aktiven Bereichs. Aber manchmal ist es erforderlich, alle Ränder innerhalb eines Aufrufs einer einzigen Methode zu setzen. Genau das macht die Methode — sie setzt neue Werte des aktiven Bereichsrandes versetzt vom Formularrand mit den Aufrufen der entsprechenden Methoden.
Damit ist die Erstellung der ersten Version des Formularobjekts abgeschlossen. Lassen Sie uns die Ergebnisse testen.
Test
Um den Test durchzuführen, erstellen wir ein einzelnes Formularobjekt im Diagramm und versuchen, es mit dem Cursor zu verschieben. Außerdem werde ich die Zustände der Maustasten und der Strg-/Umschalttasten anzeigen, sowie den Status des Cursors relativ zum aktiven Formularbereich und den Rändern.
Erstellen wir in \MQL5\Experts\TestDoEasy\Part73\ die neue EA-Datei TestDoEasyPart73.mq5.
Wir geben beim Erstellen der EA-Datei an, dass wir den Eingabeparameter InpMovable vom Typ bool und den Initialwert true benötigen:
Als Nächstes geben wir an, dass wir zusätzlich die Funktion OnChartEvent() benötigen:
Als Ergebnis erhalten wir das folgende EA-Arbeitsstück:
//+------------------------------------------------------------------+ //| TestDoEasyPart73.mq5 | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //--- input parameters input bool InpMovable=true; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- } //+------------------------------------------------------------------+
Binden wir die neu erstellte Klasse des Formularobjekts in die EA-Datei ein und deklarieren die beiden globalen Variablen — Objektname Präfix und CForm-Klassenobjekt:
//+------------------------------------------------------------------+ //| TestDoEasyPart73.mq5 | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //--- includes #include <DoEasy\Objects\Graph\Form.mqh> //--- input parameters sinput bool InpMovable = true; // Movable flag //--- global variables string prefix; CForm form; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+
In OnInit() aktivieren wir die Berechtigung zum Senden von Mauscursor-Bewegungs- und Maus-Scroll-Ereignissen, setzen den Wert für Objektnamenspräfixe als (Dateiname)+"_" und erstellen das Formularobjekt im Chart. Nach dem Erstellen setzen wir einen Offset von 10 Pixeln für die Grenzen der aktiven Zone ein:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set the permissions to send cursor movement and mouse scroll events ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true); ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true); //--- Set EA global variables prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"; //--- If the form is created, set an active area for it with the offset of 10 pixels from the edges if(form.CreateForm(ChartID(),0,prefix+"Form_01",300,20,100,70,clrSilver,200,InpMovable)) { form.SetActiveAreaShift(10,10,10,10); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Nun bleibt noch, OnChartEvent() des Formularobjekts vom OnChartEvent() des EA aufzurufen:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- form.OnChartEvent(id,lparam,dparam,sparam); } //+------------------------------------------------------------------+
Kompilieren Sie den EA und starten Sie ihn auf dem Chart eines Symbols:
Wie man sieht, wird der Status der Schaltflächen und des Cursors korrekt angezeigt. Das Formularobjekt bewegt sich nur, wenn es mit der Maus innerhalb seines aktiven Bereichs angefasst wird.
Beim Klicken mit der rechten und mittleren Maustaste innerhalb des Formulars werden das Kontextmenü und das Fadenkreuz-Werkzeug nicht aktiviert. Hier gibt es eine lustige Panne: Wenn wir das Fadenkreuz-Werkzeug außerhalb des Fensters aktivieren und dann mit ihm (bei gedrückter linker Maustaste) über dem aktiven Bereich des Formulars schweben, beginnt es sich zu verschieben. Dies ist ein fehlerhaftes Verhalten. Aber das ist erst der Anfang. Ich werde in den nächsten Artikeln Verbesserungen vornehmen und die neue Funktionalität in das Formularobjekt einbauen.
Was kommt als Nächstes?
Im nächsten Artikel werde ich die Entwicklung der Klasse des Formularobjekts fortsetzen.
Alle Dateien der aktuellen Version der Bibliothek sind unten zusammen mit der Test-EA-Datei für MQL5 zum Testen und Herunterladen angehängt.
Ihre Fragen und Vorschläge schreiben Sie bitte in den Kommentarteil.
*Der letzte Artikel der letzten Serie:
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/9442
- 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.