Grafiken in der Bibliothek DoEasy (Teil 73): Das Formularobjekt eines grafischen Elements

Artyom Trishkin | 24 Juni, 2021

Inhalt


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:

  1. 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.
  2. 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:

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:

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

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

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.

Zurück zum Inhalt

*Der letzte Artikel der letzten Serie:

Andere Klassen in der Bibliothek DoEasy (Teil 72): Kontrolle und Aufzeichnung der Parameter von Chart-Objekten in der Kollektion