English Русский 中文 Español 日本語 Português
preview
DoEasy. Steuerung (Teil 23): Verbesserung der WinForms-Objekte TabControl und SplitContainer

DoEasy. Steuerung (Teil 23): Verbesserung der WinForms-Objekte TabControl und SplitContainer

MetaTrader 5Beispiele | 12 Dezember 2022, 12:39
237 0
Artyom Trishkin
Artyom Trishkin

Inhalt


Konzept

Die Bibliothek verfügt über das Ereignismodell der „Kommunikation“ von grafischen Elementen mit dem Mauszeiger. Innerhalb jedes grafischen Elements gibt es „Arbeitsbereiche“, die für das eine oder andere Verhalten des Steuerelements bei der Interaktion mit der Maus verantwortlich sind. Zum Beispiel hat jedes Objekt einen aktiven Bereich. Befindet sich der Cursor in diesem Bereich, so ist eine Interaktion mit diesem Objekt möglich. Das Objekt hat auch einen Steuerbereich, in dem wir unter anderem Schaltflächen zur Formularsteuerung (Minimieren/Erweitern/Schließen usw.) platzieren können. Basierend auf dem Vorhandensein eines solchen Bereichs für ein Objekt, können wir zusätzliche Funktionen organisieren, wenn der Cursor mit diesem Bereich interagiert. Im Falle des SplitContainer-Steuerelements können wir zum Beispiel dessen Betrieb durch die Behandlung eines Ereignisses innerhalb des Steuerbereichs, dessen Position mit der Position des Splitters übereinstimmt, organisieren.

Um eine solche Funktionsweise zu organisieren, fügen wir neue Ereignisbehandlungen der Maus hinzu (wir würden sie jedoch immer noch hinzufügen, um die Cursor-Behandlung innerhalb des Kontrollbereichs zu organisieren) und machen die Arbeit des SplitContainer-Steuerelements trennbar, indem wir Ereignisse innerhalb dessen behandeln. Darüber hinaus werden wir die festgestellten Mängel in den Steuerelementen TabControl und SplitContainer beheben.

Im Allgemeinen führen die schrittweise Verfeinerung und die Korrektur von Fehlern bei der Bedienung der Steuerelemente zu einer gewissen Überarbeitung der Logik für die Erstellung grafischer Elemente. So funktioniert beispielsweise die Angabe der Basis- und Hauptobjekte für ein neu erstelltes Objekt in der bestehenden Implementierung der Logik für die Erstellung von an ein grafisches Element angehängten Objekten nicht immer korrekt. Eine fehlerhafte Übertragung dieser Werte führt dazu, dass das Objekt sein Haupt-Elternteil „nicht kennt“. Dies führt unweigerlich zu einer fehlerhaften Darstellung der grafischen Elemente, wenn sie zwischeneinander umgeschaltet werden, falls mehrere unabhängige Paneel-Objekte auf dem Chart vorhanden sind.

Das Auffinden und Korrigieren der Anzeige des Hauptelements des grafischen Objekts legt zunehmend eine Überarbeitung des Konzepts der Übergabe dieser Daten an das Objekt nahe. Anstatt sie erst nach der Erstellung des Objekts zu übergeben, wie es jetzt gemacht wird (und nicht immer korrekt), sollten wir sie gleich bei der Erstellung des grafischen Objekts übergeben — in seinem Konstruktor. Dieses Konzept wird in den nächsten Artikeln getestet und höchstwahrscheinlich auch umgesetzt werden.


Verbesserung der Bibliotheksklassen

Das SplitContainer-Steuerelement ist nun ein Kontrollbereich, der verschoben und in der Größe verändert werden kann. Daher werde ich die Namen der Konstanten in der Enumeration, die die Zustände der Maus in Bezug auf das Formular und das Mausereignis beschreibt, ändern.

In \MQL5\Include\DoEasy\Defines.mqh ändern wir die Namen der Konstanten für die oben genannten Enumeration:

//--- Within the window separator area
   MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_NOT_PRESSED, // The cursor is within the window resizing area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_PRESSED,     // The cursor is within the window resizing area, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_WHEEL,       // The cursor is within the window separator area, the mouse wheel is being scrolled
  };
//+------------------------------------------------------------------+

...

//--- Within the window separator area
   MOUSE_EVENT_INSIDE_SPLITTER_AREA_NOT_PRESSED,      // The cursor is within the window resizing area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_SPLITTER_AREA_PRESSED,          // The cursor is within the window resizing area, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_SPLITTER_AREA_WHEEL,            // The cursor is within the window separator area, the mouse wheel is being scrolled
  };
#define MOUSE_EVENT_NEXT_CODE  (MOUSE_EVENT_INSIDE_SPLITTER_AREA_WHEEL+1)  // The code of the next event after the last mouse event code
//+------------------------------------------------------------------+

Jetzt beziehen sich die Namen der Enumerationskonstanten auf den Kontrollbereich, was logisch ist und sofort alle Objekte abdeckt, für die solche Bereiche festgelegt werden, unabhängig vom Zweck dieser Bereiche für ein grafisches Objekt und ihrer Funktionalität:

//+------------------------------------------------------------------+
//| 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_FORM_NOT_PRESSED,         // The cursor is outside the form, the mouse buttons are not clicked
   MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED,             // The cursor is outside the form, the mouse button (any) is clicked
   MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL,               // The cursor is outside the form, the mouse wheel is being scrolled
//--- Within the form
   MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED,          // The cursor is inside the form, no mouse buttons are clicked
   MOUSE_FORM_STATE_INSIDE_FORM_PRESSED,              // The cursor is inside the form, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_FORM_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
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED,      // The cursor is inside the active area, left mouse button is released
//--- Within the window scrolling area
   MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED,   // The cursor is within the window scrolling area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED,       // The cursor is within the window scrolling area, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL,         // The cursor is within the window scrolling area, the mouse wheel is being scrolled
//--- Within the window resizing area
   MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_NOT_PRESSED,   // The cursor is within the window resizing area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_PRESSED,       // The cursor is within the window resizing area, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_WHEEL,         // The cursor is within the window resizing area, the mouse wheel is being scrolled
//--- Within the control area
   MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_NOT_PRESSED,  // The cursor is within the control area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED,      // The cursor is within the control area, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL,        // The cursor is within the control area, the mouse wheel is being scrolled
  };
//+------------------------------------------------------------------+
//| List of possible mouse events                                    |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_EVENT
  {
   MOUSE_EVENT_NO_EVENT = CHART_OBJ_EVENTS_NEXT_CODE, // No event
//---
   MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED,              // The cursor is outside the form, the mouse buttons are not clicked
   MOUSE_EVENT_OUTSIDE_FORM_PRESSED,                  // The cursor is outside the form, the mouse button (any) is clicked
   MOUSE_EVENT_OUTSIDE_FORM_WHEEL,                    // The cursor is outside the form, the mouse wheel is being scrolled
//--- Within the form
   MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED,               // The cursor is inside the form, no mouse buttons are clicked
   MOUSE_EVENT_INSIDE_FORM_PRESSED,                   // The cursor is inside the form, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_FORM_WHEEL,                     // The cursor is inside the form, the mouse wheel is being scrolled
//--- Within the window active area
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED,        // The cursor is inside the active area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_PRESSED,            // The cursor is inside the active area, any mouse button is clicked
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_WHEEL,              // The cursor is inside the active area, the mouse wheel is being scrolled
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_RELEASED,           // The cursor is inside the active area, left mouse button is released
//--- Within the window scrolling area
   MOUSE_EVENT_INSIDE_SCROLL_AREA_NOT_PRESSED,        // The cursor is within the window scrolling area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_SCROLL_AREA_PRESSED,            // The cursor is within the window scrolling area, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL,              // The cursor is within the window scrolling area, the mouse wheel is being scrolled
//--- Within the window resizing area
   MOUSE_EVENT_INSIDE_RESIZE_AREA_NOT_PRESSED,        // The cursor is within the window resizing area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_RESIZE_AREA_PRESSED,            // The cursor is within the window resizing area, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_RESIZE_AREA_WHEEL,              // The cursor is within the window resizing area, the mouse wheel is being scrolled
//--- Within the control area
   MOUSE_EVENT_INSIDE_CONTROL_AREA_NOT_PRESSED,       // The cursor is within the control area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_CONTROL_AREA_PRESSED,           // The cursor is within the control area, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL,             // The cursor is within the control area, the mouse wheel is being scrolled
  };
#define MOUSE_EVENT_NEXT_CODE  (MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL+1)   // The code of the next event after the last mouse event code
//+------------------------------------------------------------------+

Die Makrosubstitution MOUSE_EVENT_NEXT_CODE wird nun aus dem Wert der letzten Konstante der Enumeration möglicher Mausereignisse berechnet.


Alle grafischen Elemente in der Bibliothek haben einen Sichtbarkeitsbereich. Wenn ein grafisches Objekt an ein anderes angehängt ist und ein Teil davon über das übergeordnete Objekt hinausgeht, dann sollte dieser Teil beschnitten werden. Wenn wir feststellen, dass sich der Cursor über einem abgeschnittenen (unsichtbaren) Teil des Objekts befindet, müssen wir dies erkennen und kein Interaktionsereignis senden. Um solche Situationen zu kontrollieren, müssen wir eine Methode erstellen, die sicherstellt, dass sich der Cursor innerhalb des sichtbaren Teils des grafischen Objekts befindet und das Ergebnis als Flag zurückgibt.

Wir müssen solche Methoden hinzufügen, um die Koordinaten für den Beginn des Kontrollbereichs und seine Abmessungen (Breite und Höhe) festzulegen.

In der Klassendatei \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh für grafische Elemente sind solche Methoden im öffentlichen Abschnitt der Klasse zu deklarieren bzw. hinzuzufügen:

//--- (1) Save the graphical resource to the array and (2) restore the resource from the array
   bool              ResourceStamp(const string source);
   virtual bool      Reset(void);
   
//--- Return the cursor position relative to the (1) entire element, (2) visible part, (3) active area and (4) element control area
   bool              CursorInsideElement(const int x,const int y);
   bool              CursorInsideVisibleArea(const int x,const int y);
   bool              CursorInsideActiveArea(const int x,const int y);
   bool              CursorInsideControlArea(const int x,const int y);

//--- Create the element

...

//--- Set (1) object movability, (2) activity, (3) interaction,
//--- (4) element ID, (5) element index in the list, (6) availability and (7) shadow flag
   void              SetMovable(const bool flag)               { this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,flag);                     }
   void              SetActive(const bool flag)                { this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,flag);                      }
   void              SetInteraction(const bool flag)           { this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,flag);                 }
   void              SetID(const int id)                       { this.SetProperty(CANV_ELEMENT_PROP_ID,id);                            }
   void              SetNumber(const int number)               { this.SetProperty(CANV_ELEMENT_PROP_NUM,number);                       }
   void              SetEnabled(const bool flag)               { this.SetProperty(CANV_ELEMENT_PROP_ENABLED,flag);                     }
   void              SetShadow(const bool flag)                { this.m_shadow=flag;                                                   }
   
//--- Set the (1) X, (2) Y coordinates, (3) width and (4) height of the element control area
   void              SetControlAreaX(const int value)          { this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,value);             }
   void              SetControlAreaY(const int value)          { this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,value);             }
   void              SetControlAreaWidth(const int value)      { this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,value);         }
   void              SetControlAreaHeight(const int value)     { this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,value);        }
   
//--- Return the shift (1) of the left, (2) right, (3) top and (4) bottom edge of the element active area
   int               ActiveAreaLeftShift(void)           const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT);       }
   int               ActiveAreaRightShift(void)          const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT);      }
   int               ActiveAreaTopShift(void)            const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP);        }
   int               ActiveAreaBottomShift(void)         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM);     }
//--- Return the coordinate (1) of the left, (2) right, (3) top and (4) bottom edge of the element active area
   int               ActiveAreaLeft(void)                const { return int(this.CoordX()+this.ActiveAreaLeftShift());                 }
   int               ActiveAreaRight(void)               const { return int(this.RightEdge()-this.ActiveAreaRightShift());             }
   int               ActiveAreaTop(void)                 const { return int(this.CoordY()+this.ActiveAreaTopShift());                  }
   int               ActiveAreaBottom(void)              const { return int(this.BottomEdge()-this.ActiveAreaBottomShift());           }

//--- Return (1) X, (2) Y coordinate shift, (3) width, (4) height, (5) right and (6) lower edge of the control management area
   int               ControlAreaXShift(void)             const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X);       }
   int               ControlAreaYShift(void)             const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y);       }
   int               ControlAreaWidth(void)              const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH);   }
   int               ControlAreaHeight(void)             const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT);  }
//--- Return the coordinate (1) of the left, (2) right, (3) top and (4) bottom edge of the element control area
   int               ControlAreaLeft(void)               const { return this.CoordX()+this.ControlAreaXShift();                        }
   int               ControlAreaRight(void)              const { return this.ControlAreaLeft()+this.ControlAreaWidth();                }
   int               ControlAreaTop(void)                const { return this.CoordY()+this.ControlAreaYShift();                        }
   int               ControlAreaBottom(void)             const { return this.ControlAreaTop()+this.ControlAreaHeight();                }
//--- Return the relative coordinate (1) of the left, (2) right, (3) top and (4) bottom edge of the element control area
   int               ControlAreaLeftRelative(void)       const { return this.ControlAreaLeft()-this.CoordX();                          }
   int               ControlAreaRightRelative(void)      const { return this.ControlAreaRight()-this.CoordX();                         }
   int               ControlAreaTopRelative(void)        const { return this.ControlAreaTop()-this.CoordY();                           }
   int               ControlAreaBottomRelative(void)     const { return this.ControlAreaBottom()-this.CoordY();                        }
   
//--- Return the (1) X, (2) Y coordinates, (3) width and (4) height of the element right scroll area height

...

//--- Visibility scope height
   virtual int       VisibleAreaHeight(void)             const { return this.YSize();                                                  }
   virtual bool      SetVisibleAreaHeight(const int value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && CGBaseObj::SetYSize(value)) || only_prop)
                          {
                           this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,value);
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Set relative coordinates and size of the visible area
   void              SetVisibleArea(const int x,const int y,const int w,const int h)
                       {
                        this.SetVisibleAreaX(x,false);
                        this.SetVisibleAreaY(y,false);
                        this.SetVisibleAreaWidth(w,false);
                        this.SetVisibleAreaHeight(h,false);
                       }
//--- Sets the size of the visible area equal to the entire object
   void              ResetVisibleArea(void)                    { this.SetVisibleArea(0,0,this.Width(),this.Height());                  }
                       
//--- Return the (1) X coordinate, (2) right border, (3) Y coordinate, (4) bottom border of the visible area 

Diese Methoden werden verwendet, um das Setzen und Abrufen von Eigenschaften des Sichtbarkeitsbereichs des grafischen Objekts zu vereinfachen.


Implementierung der Methode, die die Position des Cursors in Bezug auf den sichtbaren Bereich des Elements zurückgibt:

//+-----------------------------------------------------------------------------+
//|Return the position of the cursor relative to the visible area of the element|
//+-----------------------------------------------------------------------------+
bool CGCnvElement::CursorInsideVisibleArea(const int x,const int y)
  {
   return(x>=this.CoordXVisibleArea() && x<=this.RightEdgeVisibleArea() && y>=this.CoordYVisibleArea() && y<=this.BottomEdgeVisibleArea());
  }
//+------------------------------------------------------------------+

Die aktuellen Koordinaten des Mauszeigers werden an die Methode übergeben, und das Flag für das Auffinden der angegebenen Koordinaten innerhalb des durch die mit den obigen Methoden ermittelten Koordinaten begrenzten Sichtbereichs wird zurückgegeben.


Die Methode, die die Position des Cursors in Bezug auf den Elementkontrollbereich zurückgibt:

//+------------------------------------------------------------------+
//|Return the cursor position relative to the element control area   |
//+------------------------------------------------------------------+
bool CGCnvElement::CursorInsideControlArea(const int x,const int y)
  {
   return(x>=this.ControlAreaLeft() && x<=this.ControlAreaRight() && y>=this.ControlAreaTop() && y<=this.ControlAreaBottom());
  }
//+------------------------------------------------------------------+

Dies ist eine geänderte Methode, die zuvor hinzugefügt wurde. Nun werden die Werte der Koordinaten der Kontrollzone verwendet, die mit den oben beschriebenen Methoden ermittelt wurden.

Virtuelle Behandlung von Mausereignissen, d. h. ihre Deklaration, befinden sich in der Formularobjektklasse im geschützten Abschnitt der Klasse in \MQL5\Include\DoEasy\Objects\Graph\Form.mqh.

Alle diese Ereignisbehandlungen haben hier keine Funktion und sollten in den abgeleiteten Klassen neu definiert werden. Fügen wir der Liste solcher Klassen die Deklaration neuer Ereignisbehandlungen für den Cursor hinzu, der sich im Kontrollbereich des grafischen Elements befindet:

//--- 'The cursor is inside the window scrolling area, a mouse button is clicked (any)' event handler
   virtual void      MouseScrollAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the window scrolling area, the mouse wheel is being scrolled' event handler
   virtual void      MouseScrollAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the control area, no mouse buttons are clicked' event handler
   virtual void      MouseControlAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the control area, a mouse button is clicked (any)' event handler
   virtual void      MouseControlAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the control area, the mouse wheel is being scrolled' event handler
   virtual void      MouseControlAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- Send a message about the event
   virtual bool      SendEvent(const long chart_id,const ushort event_id);

public:


Der Methode, ein neues angehängtes Element erstellt, fügen wir einen Hinweis auf das Haupt- und Basisobjekt hinzu:

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CForm::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                             const int x,
                             const int y,
                             const int w,
                             const int h,
                             const color colour,
                             const uchar opacity,
                             const bool activity,
                             const bool redraw)
  {
//--- Create a new graphical element
   CGCnvElement *obj=this.CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity);
//--- If the object has been created, draw the added object and return 'true'
   if(obj==NULL)
      return false;
   obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain());
   obj.SetBase(this.GetBase());
   obj.Erase(colour,opacity,redraw);
   return true;
  }
//+------------------------------------------------------------------+

Wenn dieses Objekt Zeiger auf das Haupt- und das Basisobjekt enthält, werden das Haupt- und das Basisobjekt für das neu erstellte grafische Objekt, das mit ihm verbunden ist, angezeigt. Leider funktioniert dieser Ansatz nicht bei allen grafischen Elementen, und die Suche nach dem Fehler hat noch keine Ergebnisse gebracht. Offensichtlich muss dieser Ansatz für die Einstellung der Haupt- und Basisobjekte geändert werden, und ich werde dies bald tun. Außerdem müssen wir bei einer Zunahme der Zahl der grafischen Elemente die Korrektheit der Eingabe der Haupt- und Basisobjekte in alle neuen Bibliotheksobjekte kontrollieren, was nicht praktikabel ist. Nachdem die Funktionalität einmal erstellt wurde, sollte sie in allen neuen Objekten der Bibliothek funktionieren, damit wir nicht über denselben Fehler stolpern und die Zeiger auf die Haupt- und Basisobjekte vor Ort für jedes neue Objekt angeben müssen.

Verbessern wir die Methode zum Setzen und Zurückgeben des Mausstatus relativ zum Formular. Es ist notwendig, die Behandlung des Cursors, der sich innerhalb des Kontrollbereichs befindet, in die Methode mit der Steuerung von Tastendrücken und dem Scrollen des Mausrads aufzunehmen. Ich werde der Einfachheit halber eine Tabelle hinzufügen, in der die Mausstatus-Flags beschrieben werden:

//+------------------------------------------------------------------+
//| Set and get 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)
  {
//--- Data location in the ushort value of the button status
   //---------------------------------------------------------------------------
   //   bit    |    byte   |            state            |    dec    |   hex   |
   //---------------------------------------------------------------------------
   //    0     |     0     | left mouse button           |     1     |    1    |
   //---------------------------------------------------------------------------
   //    1     |     0     | right mouse button          |     2     |    2    |
   //---------------------------------------------------------------------------
   //    2     |     0     | SHIFT key                   |     4     |    4    |
   //---------------------------------------------------------------------------
   //    3     |     0     | CTRL key                    |     8     |    8    |
   //---------------------------------------------------------------------------
   //    4     |     0     | middle mouse button         |    16     |   10    |
   //---------------------------------------------------------------------------
   //    5     |     0     | 1 add. mouse button         |    32     |   20    |
   //---------------------------------------------------------------------------
   //    6     |     0     | 2 add. mouse button         |    64     |   40    |
   //---------------------------------------------------------------------------
   //    7     |     0     | scrolling the wheel         |    128    |   80    |
   //---------------------------------------------------------------------------
   //---------------------------------------------------------------------------
   //    0     |     1     | cursor inside the form      |    256    |   100   |
   //---------------------------------------------------------------------------
   //    1     |     1     | cursor inside active area   |    512    |   200   |
   //---------------------------------------------------------------------------
   //    2     |     1     | cursor in the control area  |   1024    |   400   |
   //---------------------------------------------------------------------------
   //    3     |     1     | cursor in the scrolling area|   2048    |   800   |
   //---------------------------------------------------------------------------
   //    4     |     1     | cursor at the left edge     |   4096    |  1000   |
   //---------------------------------------------------------------------------
   //    5     |     1     | cursor at the bottom edge   |   8192    |  2000   |
   //---------------------------------------------------------------------------
   //    6     |     1     | cursor at the right edge    |   16384   |  4000   |
   //---------------------------------------------------------------------------
   //    7     |     1     | cursor at the top edge      |   32768   |  8000   |
   //---------------------------------------------------------------------------
//--- Get the mouse status relative to the form, as well as the states of mouse buttons and Shift/Ctrl keys
   this.m_mouse_form_state=MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED;
   ENUM_MOUSE_BUTT_KEY_STATE state=this.m_mouse.ButtonKeyState(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(CGCnvElement::CursorInsideElement(this.m_mouse.CoordX(),this.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(CGCnvElement::CursorInsideActiveArea(this.m_mouse.CoordX(),this.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 the cursor is inside the control area, set bit 10 "cursor inside the control area",
      if(CGCnvElement::CursorInsideControlArea(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
         this.m_mouse_state_flags |= (0x0001<<10);
      //--- otherwise, remove the "cursor inside the control area" bit
      else this.m_mouse_state_flags &=0xFBFF;
      
      //--- If one of the three mouse buttons is pressed, check the location of the cursor in the form areas and
      //--- return the appropriate value of the pressed key (in the active, control or form area)
      if((this.m_mouse_state_flags & 0x0001)!=0 || (this.m_mouse_state_flags & 0x0002)!=0 || (this.m_mouse_state_flags & 0x0010)!=0)
        {
         //--- If the cursor is inside the form
         if((this.m_mouse_state_flags & 0x0100)!=0)
            this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_FORM_PRESSED;
         //--- If the cursor is inside the active area of the form
         if((this.m_mouse_state_flags & 0x0200)!=0)
            this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED;
         //--- If the cursor is inside the form control area
         if((this.m_mouse_state_flags & 0x0400)!=0)
            this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED;
        }
      
      //--- otherwise, if not a single mouse button is pressed
      else
        {
         //--- if the mouse wheel is scrolled, return the appropriate wheel scrolling value (in the active, control or form area)
         //--- If the cursor is inside the form
         if((this.m_mouse_state_flags & 0x0100)!=0)
           {
            //--- If the mouse wheel is being scrolled
            if((this.m_mouse_state_flags & 0x0080)!=0)
               this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_FORM_WHEEL;
            else
               this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED;
           }
         //--- If the cursor is inside the active area of the form
         if((this.m_mouse_state_flags & 0x0200)!=0)
           {
            //--- If the mouse wheel is being scrolled
            if((this.m_mouse_state_flags & 0x0080)!=0)
               this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL;
            else
               this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED;
           }
         //--- If the cursor is inside the form control area
         if((this.m_mouse_state_flags & 0x0400)!=0)
           {
            //--- If the mouse wheel is being scrolled
            if((this.m_mouse_state_flags & 0x0080)!=0)
               this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL;
            else
               this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_NOT_PRESSED;
           }
        } 
     }
//--- If the cursor is outside the form
   else
     {
      //--- return the appropriate button value in an inactive area
      this.m_mouse_form_state=
        (
         ((this.m_mouse_state_flags & 0x0001)!=0 || (this.m_mouse_state_flags & 0x0002)!=0 || (this.m_mouse_state_flags & 0x0010)!=0) ? 
          MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED
        );
     }
   return this.m_mouse_form_state;
  }
//+------------------------------------------------------------------+

Die Logik der Methode ist in den Kommentaren beschrieben. Anhand des Zustands der Bit-Flags in der Variablen m_mouse_state_flags bestimmen wir, ob die Maustaste gedrückt ist oder nicht. Außerdem verwenden wir sie, um die Position des Cursors in einem bestimmten Bereich eines grafischen Objekts zu bestimmen und den endgültigen Zustand des Cursors, der Schaltflächen und des Mausrads relativ zum Formular zurückzugeben.


Der Maus-Ereignishandlung fügen wir die Behandlung neuer Ereignisse hinzu:

//+------------------------------------------------------------------+
//| Mouse event handler                                              |
//+------------------------------------------------------------------+
void CForm::OnMouseEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   switch(id)
     {
      //--- The cursor is outside the form, the mouse buttons are not clicked
      //--- The cursor is outside the form, any mouse button is clicked
      //--- The cursor is outside the form, the mouse wheel is being scrolled
      case MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED          :
      case MOUSE_EVENT_OUTSIDE_FORM_PRESSED              :
      case MOUSE_EVENT_OUTSIDE_FORM_WHEEL                :
        break;
      //--- The cursor is inside the form, the mouse buttons are not clicked
      case MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED           :  this.MouseInsideNotPressedHandler(id,lparam,dparam,sparam);       break;
      //--- The cursor is inside the form, any mouse button is clicked
      case MOUSE_EVENT_INSIDE_FORM_PRESSED               :  this.MouseInsidePressedHandler(id,lparam,dparam,sparam);          break;
      //--- The cursor is inside the form, the mouse wheel is being scrolled
      case MOUSE_EVENT_INSIDE_FORM_WHEEL                 :  this.MouseInsideWhellHandler(id,lparam,dparam,sparam);            break;
      //--- The cursor is inside the active area, the mouse buttons are not clicked
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED    :  this.MouseActiveAreaNotPressedHandler(id,lparam,dparam,sparam);   break;
      //--- The cursor is inside the active area, any mouse button is clicked
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_PRESSED        :  this.MouseActiveAreaPressedHandler(id,lparam,dparam,sparam);      break;
      //--- The cursor is inside the active area, the mouse wheel is being scrolled
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_WHEEL          :  this.MouseActiveAreaWhellHandler(id,lparam,dparam,sparam);        break;
      //--- The cursor is inside the active area, left mouse button is released
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_RELEASED       :  this.MouseActiveAreaReleasedHandler(id,lparam,dparam,sparam);     break;
      //--- The cursor is within the window scrolling area, the mouse buttons are not clicked
      case MOUSE_EVENT_INSIDE_SCROLL_AREA_NOT_PRESSED    :  this.MouseScrollAreaNotPressedHandler(id,lparam,dparam,sparam);   break;
      //--- The cursor is within the window scrolling area, any mouse button is clicked
      case MOUSE_EVENT_INSIDE_SCROLL_AREA_PRESSED        :  this.MouseScrollAreaPressedHandler(id,lparam,dparam,sparam);      break;
      //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled
      case MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL          :  this.MouseScrollAreaWhellHandler(id,lparam,dparam,sparam);        break;
      //--- The cursor is within the control area, the mouse buttons are not clicked
      case MOUSE_EVENT_INSIDE_CONTROL_AREA_NOT_PRESSED   :  this.MouseControlAreaNotPressedHandler(id,lparam,dparam,sparam);  break;
      //--- The cursor is within the control area, the mouse button (any) is clicked
      case MOUSE_EVENT_INSIDE_CONTROL_AREA_PRESSED       :  this.MouseControlAreaPressedHandler(id,lparam,dparam,sparam);     break;
      //--- The cursor is within the control area, the mouse wheel is being scrolled
      case MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL         :  this.MouseControlAreaWhellHandler(id,lparam,dparam,sparam);       break;
      //--- MOUSE_EVENT_NO_EVENT
      default: break;
     }
   this.m_mouse_event_last=(ENUM_MOUSE_EVENT)id;
  }
//+------------------------------------------------------------------+

Wenn die Ereignis-ID, die an die Methode übergeben wird, der Cursor im Kontrollbereich + gedrückte/nicht gedrückte Maustasten + Radstatus ist, dann werden die entsprechenden virtuellen Methoden, die ich oben deklariert habe, aufgerufen. Ihre vollständige Implementierung sollte in abgeleiteten Klassen erfolgen. All diese Methoden bringen hier nichts, aber sie sollten trotzdem implementiert werden:

//+------------------------------------------------------------------+
//| The cursor is inside the control area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CForm::MouseControlAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   return;
  }
//+------------------------------------------------------------------+
//| The cursor is inside the control area,                           |
//| a mouse button is clicked (any)                                  |
//+------------------------------------------------------------------+
void CForm::MouseControlAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   return;
  }
//+------------------------------------------------------------------+
//| The cursor is inside the control area,                           |
//| the mouse wheel is being scrolled                                |
//+------------------------------------------------------------------+
void CForm::MouseControlAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   return;
  }
//+------------------------------------------------------------------+


In der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqh der Klasse TabControl, und zwar in der Methode zum Erzeugen einer bestimmten Anzahl von Registerkarten, sind Ergänzungen vorzunehmen oder die Logik in allen Zeilen der Methoden zu ändern, die das Hauptobjekt angeben:

//+------------------------------------------------------------------+
//| Create the specified number of tabs                              |
//+------------------------------------------------------------------+
bool CTabControl::CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="")
  {
//--- Calculate the size and initial coordinates of the tab title
   int w=(tab_w==0 ? this.ItemWidth()  : tab_w);
   int h=(tab_h==0 ? this.ItemHeight() : tab_h);

//--- In the loop by the number of tabs
   CTabHeader *header=NULL;
   CTabField  *field=NULL;
   for(int i=0;i<total;i++)
     {
      //--- Depending on the location of tab titles, set their initial coordinates
      int header_x=2;
      int header_y=2;
      int header_w=w;
      int header_h=h;
      
      //--- Set the current X and Y coordinate depending on the location of the tab headers
      switch(this.Alignment())
        {
         case CANV_ELEMENT_ALIGNMENT_TOP     :
           header_w=w;
           header_h=h;
           header_x=(header==NULL ? 2 : header.RightEdgeRelative());
           header_y=2;
           break;
         case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
           header_w=w;
           header_h=h;
           header_x=(header==NULL ? 2 : header.RightEdgeRelative());
           header_y=this.Height()-header_h-2;
           break;
         case CANV_ELEMENT_ALIGNMENT_LEFT    :
           header_w=h;
           header_h=w;
           header_x=2;
           header_y=(header==NULL ? this.Height()-header_h-2 : header.CoordYRelative()-header_h);
           break;
         case CANV_ELEMENT_ALIGNMENT_RIGHT   :
           header_w=h;
           header_h=w;
           header_x=this.Width()-header_w-2;
           header_y=(header==NULL ? 2 : header.BottomEdgeRelative());
           break;
         default:
           break;
        }
      //--- Create the TabHeader object
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,header_x,header_y,header_w,header_h,clrNONE,255,this.Active(),false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1));
         return false;
        }
      header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,i);
      if(header==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1));
         return false;
        }
      header.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
      header.SetBase(this.GetObject());
      header.SetPageNumber(i);
      header.SetGroup(this.Group()+1);
      header.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true);
      header.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN);
      header.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER);
      header.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true);
      header.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON);
      header.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON);
      header.SetBorderStyle(FRAME_STYLE_SIMPLE);
      header.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
      header.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN);
      header.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER);
      header.SetAlignment(this.Alignment());
      header.SetPadding(this.HeaderPaddingWidth(),this.HeaderPaddingHeight(),this.HeaderPaddingWidth(),this.HeaderPaddingHeight());
      if(header_text!="" && header_text!=NULL)
         this.SetHeaderText(header,header_text+string(i+1));
      else
         this.SetHeaderText(header,"TabPage"+string(i+1));
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT)
         header.SetFontAngle(90);
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT)
         header.SetFontAngle(270);
      header.SetTabSizeMode(this.TabSizeMode());
      
      //--- Save the initial height of the header and set its size in accordance with the header size setting mode
      int h_prev=header_h;
      header.SetSizes(header_w,header_h);
      //--- Get the Y offset of the header position after changing its height and
      //--- shift it by the calculated value only for headers on the left
      int y_shift=header.Height()-h_prev;
      if(header.Move(header.CoordX(),header.CoordY()-(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT ? y_shift : 0)))
        {
         header.SetCoordXRelative(header.CoordX()-this.CoordX());
         header.SetCoordYRelative(header.CoordY()-this.CoordY());
        }
      header.SetVisibleFlag(this.IsVisible(),false);
      //--- In the header, set the pointer to the previous object in the list
      CTabHeader *prev=this.GetTabHeader(i-1);
      header.SetPrevHeader(prev);
      
      //--- Depending on the location of the tab headers, set the initial coordinates of the tab fields
      int field_x=0;
      int field_y=0;
      int field_w=this.Width();
      int field_h=this.Height()-header.Height()-2;
      int header_shift=0;
      
      switch(this.Alignment())
        {
         case CANV_ELEMENT_ALIGNMENT_TOP     :
           field_x=0;
           field_y=header.BottomEdgeRelative();
           field_w=this.Width();
           field_h=this.Height()-header.Height()-2;
           break;
         case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
           field_x=0;
           field_y=0;
           field_w=this.Width();
           field_h=this.Height()-header.Height()-2;
           break;
         case CANV_ELEMENT_ALIGNMENT_LEFT    :
           field_x=header.RightEdgeRelative();
           field_y=0;
           field_h=this.Height();
           field_w=this.Width()-header.Width()-2;
           break;
         case CANV_ELEMENT_ALIGNMENT_RIGHT   :
           field_x=0;
           field_y=0;
           field_h=this.Height();
           field_w=this.Width()-header.Width()-2;
           break;
         default:
           break;
        }
      
      //--- Create the TabField object (tab field)
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,field_x,field_y,field_w,field_h,clrNONE,255,true,false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1));
         return false;
        }
      field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,i);
      if(field==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1));
         return false;
        }
      field.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
      field.SetBase(this.GetObject());
      field.SetPageNumber(i);
      field.SetGroup(this.Group()+1);
      field.SetBorderSizeAll(1);
      field.SetBorderStyle(FRAME_STYLE_SIMPLE);
      field.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true);
      field.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true);
      field.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
      field.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
      field.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
      field.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
      field.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
      field.SetForeColor(CLR_DEF_FORE_COLOR,true);
      field.SetPadding(this.FieldPaddingLeft(),this.FieldPaddingTop(),this.FieldPaddingRight(),this.FieldPaddingBottom());
      field.Hide();
     }
//--- Create the left-right button object
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,this.Width()-32,0,15,15,clrNONE,255,this.Active(),false);
//--- Get the pointer to a newly created object
   CArrowLeftRightBox *box_lr=this.GetArrLeftRightBox();
   if(box_lr!=NULL)
     {
      this.SetVisibleLeftRightBox(false);
      this.SetSizeLeftRightBox(box_lr.Width());
      box_lr.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
      box_lr.SetBase(this.GetObject());
      box_lr.SetID(this.GetMaxIDAll());
      box_lr.SetBorderStyle(FRAME_STYLE_NONE);
      box_lr.SetBackgroundColor(CLR_CANV_NULL,true);
      box_lr.SetOpacity(0);
      box_lr.Hide();
      CArrowLeftButton *lb=box_lr.GetArrowLeftButton();
      if(lb!=NULL)
        {
         lb.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         lb.SetBase(box_lr);
         lb.SetID(this.GetMaxIDAll());
        }
      CArrowRightButton *rb=box_lr.GetArrowRightButton();
      if(rb!=NULL)
        {
         rb.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         rb.SetBase(box_lr);
         rb.SetID(this.GetMaxIDAll());
        }
     }
//--- Create the up-down button object
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,0,this.Height()-32,15,15,clrNONE,255,this.Active(),false);
//--- Get the pointer to a newly created object
   CArrowUpDownBox *box_ud=this.GetArrUpDownBox();
   if(box_ud!=NULL)
     {
      this.SetVisibleUpDownBox(false);
      this.SetSizeUpDownBox(box_ud.Height());
      box_ud.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
      box_ud.SetBase(this.GetObject());
      box_ud.SetID(this.GetMaxIDAll());
      box_ud.SetBorderStyle(FRAME_STYLE_NONE);
      box_ud.SetBackgroundColor(CLR_CANV_NULL,true);
      box_ud.SetOpacity(0);
      box_ud.Hide();
      CArrowDownButton *db=box_ud.GetArrowDownButton();
      if(db!=NULL)
        {
         db.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         db.SetBase(box_ud);
         db.SetID(this.GetMaxIDAll());
        }
      CArrowUpButton *ub=box_ud.GetArrowUpButton();
      if(ub!=NULL)
        {
         ub.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         ub.SetBase(box_ud);
         ub.SetID(this.GetMaxIDAll());
        }
     }
//--- Arrange all titles in accordance with the specified display modes and select the specified tab
   this.ArrangeTabHeaders();
   this.Select(selected_page,true);
   return true;
  }
//+------------------------------------------------------------------+

Die Methode ist recht umfangreich, aber sie wird hier vollständig wiedergegeben, um ihre Logik zu verstehen und zu sehen, welcher Zeiger von welchem Objekt als Basis und als Hauptzeiger angegeben wird.

Anstatt einfach das Hauptobjekt zu spezifizieren

SetMain(this.GetMain());

Jetzt prüfen wir, ob das Hauptobjekt das aktuelle ist. Wenn ja, übergeben wir den Zeiger darauf, andernfalls geben den Zeiger auf das Hauptobjekt:

SetMain(this.IsMain() ? this.GetObject() : this.GetMain());


Wenn die Tabulator-Kopfzeilen in TabControl auf einer Zeichenkette liegen und ihre Anzahl es nicht zulässt, dass alle Kopfzeilen in die Größe ihres Containers passen, kann die Kopfzeile mit Hilfe der Pfeiltasten verschoben werden. Die ausgewählte Kopfzeile ist immer um zwei Pixel auf jeder Seite größer als die nicht ausgewählte. Wenn wir die Kopfzeile so verschieben, dass die ausgewählte Kopfzeile z. B. über den linken Rand des Containers hinausgeht, und dann das Formular, auf dem sich TabControl befindet, verschieben, dann wird ein kleiner Teil der ausgewählten Kopfzeile, der über den Rand hinausgegangen ist, sichtbar:

Dies geschieht, weil die Größe der ausgewählten Kopfzeile immer größer ist als die Größe der nicht ausgewählten Kopfzeile und jede Kopfzeile, die über den Rand der ausgewählten Kopfzeile hinausgeht, auf die Größe der nicht ausgewählten Kopfzeile beschnitten wird. Um dies zu verhindern, müssen wir berücksichtigen, dass die Grenzen des sichtbaren Bereichs des Containers so angepasst werden müssen, dass die ausgewählte Kopfzeile ein wenig mehr beschnitten wird als die nicht ausgewählte.

Verbessern wir in der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\TabHeader.mqh der Header-Klasse die Methode, mit der das durch den berechneten rechteckigen Sichtbarkeitsbereich umrissene Bild zugeschnitten wird. Wir verringern die Größe des Kopfzeilenausschnitts (wenn die Pfeilschaltflächen nach oben/unten sichtbar sind) um zwei Pixel, da die daraus resultierende Auffüllung zwischen der Kopfzeile und den Schaltflächen zu groß ist und nicht ordentlich aussieht. Außerdem passen wir die Koordinate des Sichtbarkeitsbereichs des Containers für den ausgewählten Registerkartenkopf an, der über den Rand hinausgegangen ist:

//+------------------------------------------------------------------+
//| Crop the image outlined by the calculated                        |
//| rectangular visibility scope                                     |
//+------------------------------------------------------------------+
void CTabHeader::Crop(void)
  {
//--- Get the pointer to the base object
   CGCnvElement *base=this.GetBase();
//--- If the object does not have a base object it is attached to, then there is no need to crop the hidden areas - leave
   if(base==NULL)
      return;
//--- Set the initial coordinates and size of the visibility scope to the entire object
   int vis_x=0;
   int vis_y=0;
   int vis_w=this.Width();
   int vis_h=this.Height();
//--- Set the size of the top, bottom, left and right areas that go beyond the container
   int crop_top=0;
   int crop_bottom=0;
   int crop_left=0;
   int crop_right=0;
//--- Get the additional size, by which to crop the titles when the arrow buttons are visible
   int add_size_lr=(this.IsVisibleLeftRightBox() ? this.m_arr_butt_lr_size : 0);
   int add_size_ud=(this.IsVisibleUpDownBox()    ? this.m_arr_butt_ud_size-2 : 0);
   int correct_size_vis=(this.State() ? 0 : 2);
//--- Calculate the boundaries of the container area, inside which the object is fully visible
   int top=fmax(base.CoordY()+(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_TOP),base.CoordYVisibleArea())+correct_size_vis+(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT ? add_size_ud : 0);
   int bottom=fmin(base.BottomEdge()-(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_BOTTOM),base.BottomEdgeVisibleArea()+1)-correct_size_vis-(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT ? add_size_ud : 0);
   int left=fmax(base.CoordX()+(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_LEFT),base.CoordXVisibleArea())+correct_size_vis;
   int right=fmin(base.RightEdge()-(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_RIGHT),base.RightEdgeVisibleArea()+1)-add_size_lr;

//--- Adjust the coordinate of the visible area if the selected tab header has gone beyond the left or bottom edge of the area
   if(this.State())
     {
      if((this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP || this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM) && this.CoordX()<left)
         left+=4;
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT && this.BottomEdge()>bottom)
         bottom-=4;
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT && this.CoordY()<top)
         top+=4;
     }

//--- Calculate the values of the top, bottom, left and right areas, at which the object goes beyond
//--- the boundaries of the container area, inside which the object is fully visible
   crop_top=this.CoordY()-top;
   if(crop_top<0)
      vis_y=-crop_top;
   crop_bottom=bottom-this.BottomEdge()-1;
   if(crop_bottom<0)
      vis_h=this.Height()+crop_bottom-vis_y;
   crop_left=this.CoordX()-left;
   if(crop_left<0)
      vis_x=-crop_left;
   crop_right=right-this.RightEdge()-1;
   if(crop_right<0)
      vis_w=this.Width()+crop_right-vis_x;
//--- If there are areas that need to be hidden, call the cropping method with the calculated size of the object visibility scope
   if(crop_top<0 || crop_bottom<0 || crop_left<0 || crop_right<0)
      this.Crop(vis_x,vis_y,vis_w,vis_h);
  }
//+------------------------------------------------------------------+

Nach diesen Verbesserungen wird der Abstand zwischen vertikal platzierten Kopfzeilen (links/rechts) und ihren Bildlaufschaltflächen sauberer sein und besser aussehen, und wenn die ausgewählte Kopfzeile über den linken oder unteren Rand des Containers hinausgeht, wird sie nicht mehr teilweise angezeigt, wenn das Formular verschoben wird.

Doch hier tritt ein weiteres Problem auf: Wenn wir uns das Bild oben genau ansehen, werden wir feststellen, dass nach dem Verschieben der ausgewählten Kopfzeile vom Rand ein weißes Feld darunter verbleibt. Mit anderen Worten, der Rahmen des Feldes, der zur Kopfzeile gehört und über den Rand hinausgeht, wird nicht bis zum äußersten Rand gezeichnet. Dies ist darauf zurückzuführen, dass die Überschrift und das Registerfeld optisch wie eine Einheit aussehen sollten. Dies wird dadurch erreicht, dass zuerst der Rahmen auf das Feld gezeichnet wird und dann eine Linie in der Farbe des Feldes an der Stelle, die an das Kopffeld angrenzt, gezeichnet wird. Dadurch wird die sichtbare Linie zwischen der Kopfzeile und dem Feld gelöscht. Wenn die Kopfzeile über den Rand ragt, bleibt ein Teil dieser „Verschmelzung der Kopfzeile mit dem Feld“ visuell erhalten.

Um dieses Artefakt loszuwerden, müssen wir die Position der Kopfzeile in der Objektklasse des Registerkartenfelds in \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\TabField.mqh steuern, und zwar in der Methode, die den Elementrahmen in Abhängigkeit von der Position der Kopfzeile zeichnet. Wenn die Kopfzeile ausgewählt und über den Rand hinaus ragt, brauchen wir die Linie, die die Kopfzeile optisch mit dem Feld verschmilzt, nicht zu zeichnen:

//+------------------------------------------------------------------+
//| Draw the element frame depending on the header position          |
//+------------------------------------------------------------------+
void CTabField::DrawFrame(void)
  {
//--- Set the initial coordinates
   int x1=0;
   int y1=0;
   int x2=this.Width()-1;
   int y2=this.Height()-1;
//--- Get the tab header corresponding to the field
   CTabHeader *header=this.GetHeaderObj();
   if(header==NULL)
      return;
//--- Draw a rectangle that completely outlines the field
   this.DrawRectangle(x1,y1,x2,y2,this.BorderColor(),this.Opacity());
//--- Depending on the location of the header, draw a line on the edge adjacent to the header.
//--- The line size is calculated from the heading size and corresponds to it with a one-pixel indent on each side
//--- thus, visually the edge will not be drawn on the adjacent side of the header
   switch(header.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :
        if(header.State() && header.CoordX()<this.CoordX())
           return;
        this.DrawLine(header.CoordXRelative()+1,0,header.RightEdgeRelative()-2,0,this.BackgroundColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
        if(header.State() && header.CoordX()<this.CoordX())
           return;
        this.DrawLine(header.CoordXRelative()+1,this.Height()-1,header.RightEdgeRelative()-2,this.Height()-1,this.BackgroundColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :
        if(header.State() && header.BottomEdge()>this.BottomEdge())
           return;
        this.DrawLine(0,header.BottomEdgeRelative()-2,0,header.CoordYRelative()+1,this.BackgroundColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :
        if(header.State() && header.CoordY()<this.CoordY())
           return;
        this.DrawLine(this.Width()-1,header.BottomEdgeRelative()-2,this.Width()-1,header.CoordYRelative()+1,this.BackgroundColor(),this.Opacity());
        break;
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Nach diesen Verbesserungen an den Objekten Registerkartenkopf und Registerkartenfeld werden alle visuellen Artefakte beim Scrollen der Kopfleiste beseitigt.


Ändern wir die Logik der Interaktion zwischen der Maus und dem Trennlinie in der Objektklasse des CplitContainer-Steuerelements.

Wenn der Mauszeiger in den Kontrollbereich des Objekts (in den Trennbereich) eintritt, zeichnen wir zunächst ein gepunktetes Rechteck im Kontrollbereich, wie im SplitContainer-Steuerelement in MS Visual Studio:

Sobald wir die Maustaste in dem von diesem Rechteck umrissenen Bereich gedrückt halten, erscheint eine Trennlinie, die wir bereits verschieben können, um die Größe der Felder zu ändern. Wenn die Verschiebung abgeschlossen ist, wird die Trennlinie ausgeblendet und das gepunktete Rechteck gelöscht.

Dieses Verhalten entspricht nicht ganz dem Verhalten der Trennlinie in MS Visual Studio, aber es sieht schöner aus und führt nicht dazu, dass das schraffierte Trennlinienobjekt ständig erscheint, sondern ersetzt es durch ein unauffälliges gepunktetes Rechteck, das den Interaktionsbereich anzeigt.

In \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\SplitContainer.mqh, und zwar im öffentlichen Abschnitt der Klasse, deklarieren wir zwei Methoden zum Zeichnen leerer und gestrichelter Rechtecke:

//--- (1) set and (2) return the panel that does not change its size when the container is resized
   void              SetFixedPanel(const ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL value)
                       { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL,value);                                                       }
   ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL FixedPanel(void) const
                       { return(ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL);        }
   
   //--- Draw an (1) empty and (2) dotted rectangle
   virtual void      DrawRectangleEmpty(void);
   virtual void      DrawRectangleDotted(void);
   
//--- Create a new attached element on the specified panel

Die Methode, die ein gestricheltes Rechteck zeichnet, zeichnet das entsprechende Rechteck im Interaktionsbereich, während ein leeres Rechteck einfach das zuvor gezeichnete gestrichelte Rechteck löscht.

Deklarieren wir zwei Ereignisbehandlungen — für den Cursor im Kontrollbereich und für die gedrückte Maustaste im gleichen Bereich:

//--- Event handler
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the control area, no mouse buttons are clicked' event handler
   virtual void      MouseControlAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the control area, a mouse button is clicked (any)' event handler
   virtual void      MouseControlAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Last mouse event handler
   virtual void      OnMouseEventPostProcessing(void);


In der Methode, mit der die Paneele erstellt werden, geben wir die Haupt- und Basisobjekte für jede der erstellten Paneele sowie die Trennlinie an:

//+------------------------------------------------------------------+
//| Create the panels                                                |
//+------------------------------------------------------------------+
void CSplitContainer::CreatePanels(void)
  {
   this.m_list_elements.Clear();
   if(this.SetsPanelParams())
     {
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,this.m_panel1_x,this.m_panel1_y,this.m_panel1_w,this.m_panel1_h,clrNONE,255,true,false))
         return;
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,this.m_panel2_x,this.m_panel2_y,this.m_panel2_w,this.m_panel2_h,clrNONE,255,true,false))
         return;
      for(int i=0;i<2;i++)
        {
         CSplitContainerPanel *panel=this.GetPanel(i);
         if(panel==NULL)
            continue;
         panel.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         panel.SetBase(this.GetObject());
        }
      //---
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLITTER,this.m_splitter_x,this.m_splitter_y,this.m_splitter_w,this.m_splitter_h,clrNONE,255,true,false))
         return;
      CSplitter *splitter=this.GetSplitter();
      if(splitter!=NULL)
        {
         splitter.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         splitter.SetBase(this.GetObject());
         splitter.SetMovable(true);
         splitter.SetDisplayed(false);
         splitter.Hide();
        }
     }
  }
//+------------------------------------------------------------------+


Am Ende der Methode, die die Parameter für die Paneele festlegt, stellen wir die Koordinaten und Abmessungen des Kontrollbereichs auf die Eigenschaften ein, die durch die Trennlinie festgelegt wurden.

//+------------------------------------------------------------------+
//| Set the panel parameters                                         |
//+------------------------------------------------------------------+
bool CSplitContainer::SetsPanelParams(void)
  {
   switch(this.SplitterOrientation())
     {

      //---...
      //---...

     }
//--- Set the coordinates and sizes of the control area equal to the properties set by the separator
   this.SetControlAreaX(this.m_splitter_x);
   this.SetControlAreaY(this.m_splitter_y);
   this.SetControlAreaWidth(this.m_splitter_w);
   this.SetControlAreaHeight(this.m_splitter_h);
   return true;
  }
//+------------------------------------------------------------------+

Zuvor habe ich sie durch das Schreiben von Eigenschaften mit den Methoden SetProperty() festgelegt, was im Grunde dasselbe ist. Aber es macht meiner Meinung nach mehr Sinn.


Im Ereignisbehandlungsprogramm löschen wir das gepunktete Rechteck, das zuvor gezeichnet wurde, nachdem wir die Koordinaten und Größen der Trennlinie berechnet haben und bevor wir das Trennlinienobjekt um die angegebenen Koordinaten verschieben:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CSplitContainer::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Adjust subwindow Y shift
   CGCnvElement::OnChartEvent(id,lparam,dparam,sparam);
//--- If the event ID is moving the separator
   if(id==WF_CONTROL_EVENT_MOVING)
     {
      //--- Get the pointer to the separator object
      CSplitter *splitter=this.GetSplitter();
      if(splitter==NULL || this.SplitterFixed())
         return;
      //--- Declare the variables for separator coordinates
      int x=(int)lparam;
      int y=(int)dparam;
      //--- Depending on the separator direction,
      switch(this.SplitterOrientation())
        {
         //--- vertical position
         case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL :
           //--- Set the Y coordinate equal to the Y coordinate of the control element
           y=this.CoordY();
           //--- Adjust the X coordinate so that the separator does not go beyond the control element
           //--- taking into account the resulting minimum width of the panels
           if(x<this.CoordX()+this.Panel1MinSize())
              x=this.CoordX()+this.Panel1MinSize();
           if(x>this.CoordX()+this.Width()-this.Panel2MinSize()-this.SplitterWidth())
              x=this.CoordX()+this.Width()-this.Panel2MinSize()-this.SplitterWidth();
           break;
         //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL
         //--- horizontal position of the separator
         default:
           //--- Set the X coordinate equal to the X coordinate of the control element
           x=this.CoordX();
           //--- Adjust the Y coordinate so that the separator does not go beyond the control element
           //--- taking into account the resulting minimum height of the panels
           if(y<this.CoordY()+this.Panel1MinSize())
              y=this.CoordY()+this.Panel1MinSize();
           if(y>this.CoordY()+this.Height()-this.Panel2MinSize()-this.SplitterWidth())
              y=this.CoordY()+this.Height()-this.Panel2MinSize()-this.SplitterWidth();
           break;
        }
      //--- Draw an empty rectangle
      this.DrawRectangleEmpty();
      //--- If the separator is shifted by the calculated coordinates,
      if(splitter.Move(x,y,true))
        {
         //--- set the separator relative coordinates
         splitter.SetCoordXRelative(splitter.CoordX()-this.CoordX());
         splitter.SetCoordYRelative(splitter.CoordY()-this.CoordY());
         //--- Depending on the direction of the separator, set its new coordinates
         this.SetSplitterDistance(!this.SplitterOrientation() ? splitter.CoordX()-this.CoordX() : splitter.CoordY()-this.CoordY(),false);
        }
     }
  }
//+------------------------------------------------------------------+

Wir lassen immer ein gepunktetes Rechteck erscheinen, wenn wir den Mauszeiger über den Trennbereich bewegen. Nach Drücken der Maustaste auf dem von diesem Rechteck umrandeten Kontrollbereich erscheint ein Trennlinienobjekt, das mit der Maus erfasst und verschoben wird. Bevor wir es verschieben, müssen wir den gezeichneten, gepunkteten Bereich löschen. Das ist genau das, was die Methode tut, indem sie ein leeres Rechteck an dieser Stelle zeichnet.


Der Cursor befindet sich innerhalb des Kontrollbereichs in der Ereignisbehandlung von „Es wurden keine Maustasten angeklickt“:

//+------------------------------------------------------------------+
//| The cursor is inside the control area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CSplitContainer::MouseControlAreaNotPressedHandler(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- If the separator is non-movable, leave
   if(this.SplitterFixed())
      return;
//--- Draw an empty rectangle in the control area
   this.DrawRectangleEmpty();
//--- Draw a dotted rectangle in the control area
   this.DrawRectangleDotted();
//--- Get the pointer to the separator
   CSplitter *splitter=this.GetSplitter();
   if(splitter==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER));
      return;
     }
//--- If the separator is not displayed
   if(!splitter.Displayed())
     {
      //--- Enable the display of the separator and show it
      splitter.SetDisplayed(true);
      splitter.Erase(true);
      splitter.Show();
     }
  }
//+------------------------------------------------------------------+

Sobald der Mauszeiger über den Kontrollbereich bewegt wird, erscheint das entsprechende Ereignis und wird an die Ereignishandlung des Formularobjekts gesendet. Die virtuelle Ereignisbehandlung wird von ihm aufgerufen. Die Implementierung der Ereignisbehandlung wird hier vorgestellt. Im Falle einer festen Trennlinie sind keine Maßnahmen erforderlich. Wir verlassen die Ereignisbehandlung. Als Nächstes löschen wir die Trennlinie und zeichnen ein gepunktetes Rechteck darauf. Wenn das Trennlinienobjekt ein Ausblendungs-Flag hat, entfernen wir dieses Flag, löschen das Trennlinienobjekt vollständig und zeigen es an. Bei diesem Ansatz befindet sich das Trennlinienobjekt zwar unter dem Cursor, ist aber dennoch unsichtbar. Wenn wir mit der Maus klicken, klicken wir auf das Trennlinienobjekt und nicht auf die Unterlage des SplitContainer-Steuerelements. Dadurch wird das Trennlinienobjekt für die Bewegung vorbereitet und ein gepunktetes Rechteck wird auf dem SplitContainer-Objekt gezeichnet, um den Kontrollbereich zu umreißen.


Der Cursor befindet sich innerhalb des Kontrollbereichs der Ereignisbehandlung, eine Maustaste wurde angeklickt (beliebig):

//+------------------------------------------------------------------+
//| The cursor is inside the control area,                           |
//| a mouse button is clicked (any)                                  |
//+------------------------------------------------------------------+
void CSplitContainer::MouseControlAreaPressedHandler(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- If the separator is non-movable, leave
   if(this.SplitterFixed())
      return;
//--- Draw an empty rectangle in the control area
   this.DrawRectangleEmpty();
  }
//+------------------------------------------------------------------+

Sobald die Maustaste gedrückt wird, wird diese Ereignisbehandlung aufgerufen. Wenn die Trennlinie fixiert ist, wird die Ereignisbehandlung einfach verlassen. Andernfalls wird das gepunktete Rechteck, das zuvor gezeichnet wurde, entfernt.


In der Ereignisbehandlung des letzten Mausereignisses ersetzen wir die Namen der zuvor umbenannten Zustandskonstanten und Mausereignisse und löschen das zuvor gezeichnete gepunktete Rechteck:

//+------------------------------------------------------------------+
//| Last mouse event handler                                         |
//+------------------------------------------------------------------+
void CSplitContainer::OnMouseEventPostProcessing(void)
  {
   if(!this.IsVisible() || !this.Enabled() || !this.Displayed())
      return;
   ENUM_MOUSE_FORM_STATE state=this.GetMouseState();
   switch(state)
     {
      //--- The cursor is outside the form, the mouse buttons are not clicked
      //--- The cursor is outside the form, any mouse button is clicked
      //--- The cursor is outside the form, the mouse wheel is being scrolled
      case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED        :
      case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED            :
      case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL              :
      case MOUSE_FORM_STATE_NONE                            :
        if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED  || 
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED         || 
           this.MouseEventLast()==MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED        ||
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_CONTROL_AREA_NOT_PRESSED ||
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_CONTROL_AREA_PRESSED     ||
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL       ||
           this.MouseEventLast()==MOUSE_EVENT_NO_EVENT)
          {
            //--- Draw an empty rectangle in the control area
            this.DrawRectangleEmpty();
            //--- Get the pointer to the separator
            CSplitter *splitter=this.GetSplitter();
            if(splitter==NULL)
              {
               ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER));
               return;
              }
            splitter.SetDisplayed(false);
            splitter.Hide();
            this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT);
          }
        break;
      //--- The cursor is inside the form, the mouse buttons are not clicked
      //--- The cursor is inside the form, any mouse button is clicked
      //--- The cursor is inside the form, the mouse wheel is being scrolled
      //--- The cursor is inside the active area, the mouse buttons are not clicked
      //--- The cursor is inside the active area, any mouse button is clicked
      //--- The cursor is inside the active area, the mouse wheel is being scrolled
      //--- The cursor is inside the active area, left mouse button is released
      //--- The cursor is within the window scrolling area, the mouse buttons are not clicked
      //--- The cursor is within the window scrolling area, any mouse button is clicked
      //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled
      //--- The cursor is within the window resizing area, the mouse buttons are not clicked
      //--- The cursor is within the window resizing area, the mouse button (any) is clicked
      //--- The cursor is within the window resizing area, the mouse wheel is being scrolled
      //--- The cursor is within the window resizing area, the mouse buttons are not clicked
      //--- The cursor is within the window resizing area, the mouse button (any) is clicked
      //--- The cursor is within the window separator area, the mouse wheel is being scrolled
      case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED         :
      case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED             :
      case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL               :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL        :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED     :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL        :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_WHEEL        :
      case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_NOT_PRESSED:
      case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED    :
      case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL      :
        break;
      //--- MOUSE_EVENT_NO_EVENT
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Jedes grafische Objekt hat einen Ereignisbehandlung für das letzte Mausereignis. Sie wird aufgerufen, wenn der Cursor den Objektbereich verlässt. In dieser Ereignisbehandlung wird das gepunktete Rechteck, das den Kontrollbereich umreißt, gelöscht, wenn sich der Mauszeiger zuvor im Bereich des gesamten SplitContainer-Steuerelements oder im Bereich seiner Trennlinie (im Kontrollbereich) befand.


Die Methode, die ein leeres Rechteck zeichnet:

//+------------------------------------------------------------------+
//| Draw an empty rectangle                                          |
//+------------------------------------------------------------------+
void CSplitContainer::DrawRectangleEmpty(void)
  {
   int cx1=this.ControlAreaLeftRelative();
   int cx2=this.ControlAreaRightRelative();
   int cy1=this.ControlAreaTopRelative();
   int cy2=this.ControlAreaBottomRelative();
   this.DrawRectangleFill(cx1,cy1,cx2,cy2,CLR_CANV_NULL,0);
   this.Update();
  }
//+------------------------------------------------------------------+

Wir ermitteln die relativen Positionskoordinaten des Rechtecks, seine Breite und Höhe innerhalb des SplitContainer-Steuerelements und zeichnen ein mit einer transparenten Farbe gefülltes Rechteck mit voller Transparenz.


Die Methode, die ein gepunktetes Rechteck zeichnet:

//+------------------------------------------------------------------+
//| Draw a dotted rectangle                                          |
//+------------------------------------------------------------------+
void CSplitContainer::DrawRectangleDotted(void)
  {
   int shift=0;
   int cx1=this.ControlAreaLeftRelative();
   int cx2=fmin(this.ControlAreaRightRelative(),this.VisibleAreaWidth()+2);
   int cy1=this.ControlAreaTopRelative();
   int cy2=this.ControlAreaBottomRelative();
//--- Draw points in the next-but-one fashion along the upper border of the rectangle from left to right
   for(int x=cx1+1;x<cx2-2;x+=2)
      this.SetPixel(x,cy1,this.ForeColor(),255);
//--- Get the offset of the next point depending on where the last point was placed
   shift=((cx2-cx1-2) %2==0 ? 0 : 1);
//--- Draw points in the next-but-one fashion along the right border of the rectangle from top to bottom
   for(int y=cy1+1+shift;y<cy2-2;y+=2)
      this.SetPixel(cx2-2,y,this.ForeColor(),255);
//--- Get the offset of the next point depending on where the last point was placed
   shift=(this.ControlAreaHeight()-2 %2==0 ? 1 : 0);
//--- Draw points in the next-but-one fashion along the lower border of the rectangle from right to left
   for(int x=cx2-2-shift;x>cx1;x-=2)
      this.SetPixel(x,cy2-2,this.ForeColor(),255);
//--- Get the offset of the next point depending on where the last point was placed
   shift=((cx2-cx1-2) %2==0 ? 0 : 1);
//--- Draw points in the next-but-one fashion along the left border of the rectangle from bottom to top
   for(int y=cy2-2-shift;y>cy1;y-=2)
      this.SetPixel(cx1+1,y,this.ForeColor(),255);
//--- Update the canvas
   this.Update();
  }
//+------------------------------------------------------------------+

Die Logik der Methode wird in den Codekommentaren beschrieben. Wir müssen eine Linie mit Punkten zeichnen, die sich auf die übernächste Art und Weise befinden. Dies geschieht in vier Schleifen — von links nach rechts --> oben-unten --> von rechts nach links --> unten-oben. Der Schleifenindex wird um zwei erhöht, sodass bei jeder Iteration ein Punkt gesetzt wird, wobei der Schleifenindex als Koordinate dient. Wenn der Index also um zwei erhöht wird, können wir die Punkte in der übernächsten Reihenfolge setzen. Aber gleichzeitig gibt es eine Nuance: Wenn ein Punkt am Ende der Schleife gesetzt wurde, dann sollte der nächste Zyklus nicht an einem Punkt beginnen, um die Punkte immer in der übernächsten Reihenfolge zu setzen. Dazu berechnen wir einfach die Breite und Höhe des Rechtecks und addieren je nach Gleichmäßigkeit des resultierenden Wertes entweder 1 oder 0 zur nächsten Koordinate. Im Falle der umgekehrten Schleife wird das resultierende Inkrement von der Punktkoordinate subtrahiert. Auf diese Weise erhalten wir ein gepunktetes Rechteck, bei dem die Punkte immer einzeln gezeichnet werden. Ich könnte z. B. einfach ein angepasstes Rechteck zeichnen, indem ich die Methode DrawPolygonAA() verwende, da sie es erlaubt, den Typ der gezeichneten Linie festzulegen. Aber leider zeichnet der Linientyp STYLE_DOT in diesem Fall Liniensegmente, die länger als ein Pixel sind.


Sobald der Cursor den Kontrollbereich (Trennlinie) des SplitContainer-Steuerelements verlässt, tritt er sofort in den Bereich eines der Kontrollfelder ein oder verlässt ihn sogar. Wenn der Cursor über das Steuerelement hinausgeht, wird die Ereignisbehandlung des letzten oben beschriebenen Mausereignisses ausgelöst. Wenn der Mauszeiger über einem der Felder des SplitContainer-Steuerelements schwebt, müssen wir das Entfernen der Maus aus dem Steuerbereich des Basisobjekts in der Ereignisbehandlung dieses Feldes durch Entfernen des gepunkteten Rechtecks behandeln.

In \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\SplitContainerPanel.mqh, und zwar in der Ereignisbehandlung vopn „Der Cursor befindet sich innerhalb des aktiven Bereichs, die Maustasten sind nicht angeklickt“, fügen wir eine Zeile hinzu, der ein leeres Rechteck innerhalb des Basisobjekt-Steuerungsbereichs zeichnet (das Basisobjekt für das Paneel ist der Container des SplitContainer-Steuerelements):

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CSplitContainerPanel::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Get the pointer to the base object
   CSplitContainer *base=this.GetBase();
//--- If the base object is not received, or the separator is non-movable, leave
   if(base==NULL || base.SplitterFixed())
      return;
//--- Draw an empty rectangle in the base object control area
   base.DrawRectangleEmpty();
//--- Get the pointer to the separator object from the base object
   CSplitter *splitter=base.GetSplitter();
   if(splitter==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER));
      return;
     }
//--- If the separator is displayed
   if(splitter.Displayed())
     {
      //--- Disable the display of the separator and hide it
      splitter.SetDisplayed(false);
      splitter.Hide();
     }
  }
//+------------------------------------------------------------------+

Sobald der Cursor in das Feld eintritt, wird diese Ereignisbehandlung ausgelöst und das gepunktete Rechteck, das den Kontrollbereich umreißt, entfernt.


Verbessern wir das Hilfstrennlinienobjekt in \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\Splitter.mqh.

Nach einer kürzlichen Aktualisierung erschien beim Kompilieren der Bibliothek eine Warnung:

deprecated behavior, hidden method calling will be disabled in a future MQL compiler version    SplitContainer.mqh      758     16

Wenn wir die im Protokoll zur angegebene Adresse gehen, kommen wir in SplitContainer.mqh zur folgenden Zeile:

//+------------------------------------------------------------------+
//| The cursor is inside the control area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CSplitContainer::MouseControlAreaNotPressedHandler(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- If the separator is non-movable, leave
   if(this.SplitterFixed())
      return;
//--- Draw an empty rectangle in the control area
   this.DrawRectangleEmpty();
//--- Draw a dotted rectangle in the control area
   this.DrawRectangleDotted();
//--- Get the pointer to the separator
   CSplitter *splitter=this.GetSplitter();
   if(splitter==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER));
      return;
     }
//--- If the separator is not displayed
   if(!splitter.Displayed())
     {
      //--- Enable the display of the separator and show it
      splitter.SetDisplayed(true);
      splitter.Erase(true);
      splitter.Show();
     }
  }
//+------------------------------------------------------------------+

Dies ist eine virtuelle Methode, die den Hintergrund des grafischen Objekts vollständig löscht.

Die gleiche Methode findet sich in \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh:

//+------------------------------------------------------------------+
//| Clear the element completely                                     |
//+------------------------------------------------------------------+
void CWinFormBase::Erase(const bool redraw=false)
  {
//--- Fully clear the element with the redrawing flag
   CGCnvElement::Erase(redraw);
  }
//+------------------------------------------------------------------+

und in \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh:

//+------------------------------------------------------------------+
//| Clear the element completely                                     |
//+------------------------------------------------------------------+
void CGCnvElement::Erase(const bool redraw=false)
  {
   this.m_canvas.Erase(CLR_CANV_NULL);
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Die Signatur der Methoden ist identisch. Am Ende führt alles zu der Methode Erase() des grafischen Elementobjekts CGCnvElement. Aus diesem Grund ist mir nicht klar, warum der Compiler eine Mehrdeutigkeit sieht. Aber ich werde das in Ordnung bringen. Fügen wir Methode Erase() zu \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\Splitter.mqhhinzu. Gleichzeitig werden zwei Maus-Ereignishandlungen deklariert:

//--- Redraw the object
   virtual void      Redraw(bool redraw);
//--- Clear the element filling it with color and opacity
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Clear the element with a gradient fill
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
//--- Clear the element completely
   virtual void      Erase(const bool redraw=false) { CWinFormBase::Erase(redraw);  }
//--- 'The cursor is inside the active area, a mouse button is clicked (any)' event handler
   virtual void      MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the active area, the left mouse button is clicked' event handler
   virtual void      MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
  };
//+------------------------------------------------------------------+

Die Methode Erase() ruft einfach genau dieselbe Methode der übergeordneten Klasse auf und beseitigt so die Compilerwarnung.

In der Methode, die das Gitter zeichnet, fügen wir Transparenz hinzu (wir fügen 200 anstelle von 255 hinzu), wodurch das Trennlinienobjekt leicht transparent wird:

//+------------------------------------------------------------------+
//| Draw the grid                                                    |
//+------------------------------------------------------------------+
void CSplitter::DrawGrid(void)
  {
   for(int y=0;y<this.Height()-1;y++)
      for(int x=0;x<this.Width();x++)
         this.SetPixel(x,y,this.ForeColor(),uchar(y%2==0 ? (x%2==0 ? 200 : 0) : (x%2==0 ? 0 : 200)));
  }
//+------------------------------------------------------------------+

Die Punkte werden nun mit einer Deckkraft von 200 gezeichnet, wodurch sie leicht transparent werden und das Aussehen der Trennlinie etwas verbessert wird.


Der Cursor befindet sich innerhalb des aktiven Bereichs, eine beliebige Maustaste wurde angeklickt:

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| a mouse button is clicked (any)                                  |
//+------------------------------------------------------------------+
void CSplitter::MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- If the separator is not displayed
   if(!this.Displayed())
     {
      //--- Enable the display of the separator and show it
      this.SetDisplayed(true);
      this.Show();
     }
//--- Redraw the separator
   this.Redraw(true);
  }
//+------------------------------------------------------------------+

Die Ereignisbehandlung wird ausgelöst, wenn die Maustaste auf das Objekt geklickt wird. Wenn das Objekt noch nicht angezeigt wird, schalten wir es ein und zeigen es an.
Dann zeichnen wir das Objekt neu, sodass ein schraffiertes Rechteck auf dem Hintergrund erscheint und das Trennlinienobjekt vollständig angezeigt wird.


Der Cursor befindet sich im aktiven Bereich, die linke Maustaste wurde geklickt:

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| left mouse button released                                       |
//+------------------------------------------------------------------+
void CSplitter::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   this.SetDisplayed(false);
   this.Hide();
   ::ChartRedraw(this.ChartID());
  }
//+------------------------------------------------------------------+

Wenn wir eine Maustaste loslassen, die zuvor innerhalb des grafischen Objekts gedrückt wurde, wird diese Ereignisbehandlung aufgerufen. Hier setzen wir das Flag, dass das Trennlinienobjekt nicht gezeichnet werden soll und blenden es aus. Um die Änderungen sofort anzuzeigen, zeichnen wir das Chart neu. Wenn also die Trennlinie an eine neue Stelle verschoben wird und die Maustaste losgelassen wird, wird es ausgeblendet, da es seinen Zweck erfüllt hat — die Trennlinie des SplitContainer-Steuerelements zu verschieben.


Jetzt müssen wir die Kollektionsklasse der grafischen Elemente \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh verbessern.

Hier müssen wir die Situation behandeln, wenn sich der Cursor über dem Objekt befindet, aber über seinem verborgenen Bereich. Dies kann passieren, wenn das grafische Objekt an das Steuerelement angehängt ist und ein Teil davon über das übergeordnete Objekt hinausgeht. Wenn sich der Mauszeiger über einem verdeckten Teil befindet, ist dieses grafische Objekt an dieser Stelle unsichtbar und sollte dementsprechend nicht auf den Mauszeiger reagieren. Außerdem sollten wir bei einem Klick auf eines der Elemente, die mit dem Paneel verbunden sind, zunächst das gesamte Paneel mit allen damit verbundenen Objekten in den Vordergrund bringen und dann das angeklickte Objekt selbst in den Vordergrund bringen.

In der Methode für die Suche nach Interaktionsobjekten fügen wir die Verarbeitung der Interaktion mit ausgeblendeten Objekten hinzu. Solche Objekte sollten übersprungen werden:

//+------------------------------------------------------------------+
//| Search for interaction objects                                   |
//+------------------------------------------------------------------+
CForm *CGraphElementsCollection::SearchInteractObj(CForm *form,const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- If a non-empty pointer is passed
   if(form!=NULL)
     {
      //--- Create the list of interaction objects
      int total=form.CreateListInteractObj();
      //--- In the loop by the created list
      for(int i=total-1;i>WRONG_VALUE;i--)
        {
         //--- get the next form object
         CForm *obj=form.GetInteractForm(i);
         //--- If the object is received, but is not visible, or not active, or should not be displayed, skip it
         if(obj==NULL || !obj.IsVisible() || !obj.Enabled() || !obj.Displayed())
            continue;
         
         //--- If the form object is TabControl, return the selected tab under the cursor
         if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)
           {
            CTabControl *tab_ctrl=obj;
            CForm *elm=tab_ctrl.SelectedTabPage();
            if(elm!=NULL && elm.MouseFormState(id,lparam,dparam,sparam)>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
               return elm;
           }
         
         //--- If the form object is a SplitContainer control or a panel of the SplitContainer control,
         //--- and if the cursor is located on the area protruding beyond the panel edges, then skip such an object
         if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL)
           {
            if(!obj.CursorInsideVisibleArea(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
               continue;
           }
         
         //--- If the form object is attached to the panel of the SplitContainer control
         //--- and if the object goes beyond the edges of the panel, and the cursor is on the area protruding beyond the edges of the panel, then skip such an object
         CForm *base=obj.GetBase();
         if(base!=NULL && base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL)
           {
            if(!obj.CursorInsideVisibleArea(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
               continue;
           }
         
         //--- If the mouse cursor is over the object, return the pointer to this object
         if(obj.MouseFormState(id,lparam,dparam,sparam)>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
            return obj;
        }
     }
//--- Return the same pointer
   return form;
  }
//+------------------------------------------------------------------+

Bislang werden hier nicht alle grafischen Elemente behandelt, sondern nur solche, bei denen eine fehlerhafte Interaktion mit der Maus festgestellt wurde. Später werde ich die richtige Logik für die Handhabung der einzelnen grafischen Elemente finden. Wenn wir die gleiche Handhabung zu den Kopfzeilen der Registerkarten von TabControl hinzufügen, funktionieren sie nicht mehr, nachdem wir durch die Liste der Kopfzeilen geblättert haben. Aus diesem Grund implementiere ich noch keine universelle Handhabung der einzelnen Steuerelemente, da wir zunächst die Gründe verstehen müssen, um alles richtig zu machen.

Außerdem wurde das falsche Verhalten von Interaktionsobjekten in der Methode, die den Zeiger auf das Formular unter dem Cursor zurückgibt, aufgrund des „Verlusts des Mausstatus“ bemerkt und behoben. Wir ergänzen das Lesen des Mausstatus‘, bevor der Zeiger an das gefundene Objekt zurückgegeben wird , und die Behandlung des Objekts übersprungen wird, wenn sein Anzeigeflag deaktiviert ist:

//+------------------------------------------------------------------+
//| Return the pointer to the form located under the cursor          |
//+------------------------------------------------------------------+
CForm *CGraphElementsCollection::GetFormUnderCursor(const int id, 
                                                    const long &lparam, 
                                                    const double &dparam, 
                                                    const string &sparam,
                                                    ENUM_MOUSE_FORM_STATE &mouse_state,
                                                    long &obj_ext_id,
                                                    int &form_index)
  {
//--- Set the ID of the extended standard graphical object to -1 
//--- and the index of the anchor point managed by the form to -1
   obj_ext_id=WRONG_VALUE;
   form_index=WRONG_VALUE;
//--- Initialize the mouse status relative to the form
   mouse_state=MOUSE_FORM_STATE_NONE;
//--- Declare the pointers to graphical element collection class objects
   CGCnvElement *elm=NULL;
   CForm *form=NULL;
//--- Get the list of objects the interaction flag is set for (there should be only one object)
   CArrayObj *list=CSelect::ByGraphCanvElementProperty(GetListCanvElm(),CANV_ELEMENT_PROP_INTERACTION,true,EQUAL);
//--- If managed to obtain the list and it is not empty,
   if(list!=NULL && list.Total()>0)
     {
      //--- Get the only graphical element there
      elm=list.At(0);
      //--- If the element is a form object or its descendants
      if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE && elm.IsVisible())
        {
         //--- Assign the pointer to the element for the form object pointer
         form=elm;
         //--- Get the mouse status relative to the form
         mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
         //--- If the cursor is inside the form,
         if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
           {
            //--- Find the interaction object.
            //--- This will be either the found object or the same form
            form=this.SearchInteractObj(form,id,lparam,dparam,sparam);
            //--- Get the mouse status of the found object
            mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
            //--- Return the form object
            //Comment(form.TypeElementDescription()," ",form.Name(),", ZOrder: ",form.Zorder(),", Interaction: ",form.Interaction());
            return form;
           }
        }
     }
//--- If there is no a single form object with a specified interaction flag,
//--- in the loop by all graphical element collection class objects
   int total=this.m_list_all_canv_elm_obj.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the next element
      elm=this.m_list_all_canv_elm_obj.At(i);
      if(elm==NULL || !elm.IsVisible() || !elm.Enabled() || !elm.Displayed())
         continue;
      //--- if the obtained element is a form object or its descendants
      if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE)
        {
         //--- Assign the pointer to the element for the form object pointer
         form=elm;
         //--- Get the mouse status relative to the form
         mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
         //--- If the cursor is within the form, return the pointer to the form
         if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
           {
            //--- Find the interaction object.
            //--- This will be either the found object or the same form
            form=this.SearchInteractObj(form,id,lparam,dparam,sparam);
            //--- Get the mouse status of the found object
            mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
            //--- Return the form object
            //Comment(form.TypeElementDescription()," ",form.Name(),", ZOrder: ",form.Zorder(),", Interaction: ",form.Interaction());
            return form;
           }
        }
     }
//--- If there is no a single form object from the collection list
//--- Get the list of extended standard graphical objects
   list=this.GetListStdGraphObjectExt();
   if(list!=NULL)
     {
      //--- in the loop by all extended standard graphical objects
      for(int i=0;i<list.Total();i++)
        {
         //--- get the next graphical object,
         CGStdGraphObj *obj_ext=list.At(i);
         if(obj_ext==NULL)
            continue;
         //--- get the object of its toolkit,
         CGStdGraphObjExtToolkit *toolkit=obj_ext.GetExtToolkit();
         if(toolkit==NULL)
            continue;
         //--- handle the event of changing the chart for the current graphical object
         obj_ext.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam);
         //--- Get the total number of form objects created for the current graphical object
         total=toolkit.GetNumControlPointForms();
         //--- In the loop by all form objects
         for(int j=0;j<total;j++)
           {
            //--- get the next form object,
            form=toolkit.GetControlPointForm(j);
            if(form==NULL)
               continue;
            //--- get the mouse status relative to the form
            mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
            //--- If the cursor is inside the form,
            if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
              {
               //--- set the object ID and form index
               //--- and return the pointer to the form
               obj_ext_id=obj_ext.ObjectID();
               form_index=j;
               return form;
              }
           }
        }
     }
//--- Nothing is found - return NULL
   .return NULL;
  }
//+------------------------------------------------------------------+


In der Ereignisbehandlung des Cursor-Verarbeitungsblocks fügen wir innerhalb des Formulars bei gedrückter Maustaste eine vorläufige Anzeige des gesamten Formulars in den Vordergrund hinzu und zeichnen dann, nach dem Aufruf der Ereignisbehandlung des Formularobjekts, das Chart neu, um die Änderungen sofort anzuzeigen:

            //+---------------------------------------------------------------------------------------------+
            //| 'The cursor is inside the form, a mouse button is clicked (any)' event handler              |
            //+---------------------------------------------------------------------------------------------+
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_PRESSED)
              {
               this.SetChartTools(::ChartID(),false);
               //--- If the flag of holding the form is not set yet
               if(!pressed_form)
                 {
                  pressed_form=true;      // set the flag of pressing on the form
                  pressed_chart=false;    // disable the flag of pressing on the form
                 }
               CForm *main=form.GetMain();
               if(main!=NULL)
                  main.BringToTop();
               form.OnMouseEvent(MOUSE_EVENT_INSIDE_FORM_PRESSED,lparam,dparam,sparam);
               ::ChartRedraw(form.ChartID());
              }
            //+---------------------------------------------------------------------------------------------+

Hier erhalten wir den Zeiger auf das Hauptobjekt. Wenn er empfangen wird, wird das gesamte Objekt mit allen ihm zugeordneten Elementen in den Vordergrund gestellt. Dann rufen wir die Ereignisbehandlung des Form-Objekts auf, mit dem die Interaktion stattfindet, und aktualisieren schließlich das Chart.


Ich werde ähnliche Verbesserungen im Block zur Behandlung des Cursors innerhalb des aktiven Bereichs vornehmen, wenn die Maustaste gedrückt wird:

            //+---------------------------------------------------------------------------------------------+
            //| 'The cursor is inside the active area,  any mouse button is clicked' event handler          |
            //+---------------------------------------------------------------------------------------------+
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED && !move)
              {
               pressed_form=true;                                       // the flag of holding the mouse button on the form
               //--- If the left mouse button is pressed
               if(this.m_mouse.IsPressedButtonLeft())
                 {
                  //--- Set flags and form parameters
                  move=true;                                            // movement flag
                  form.SetInteraction(true);                            // flag of the form interaction with the environment
                  CForm *main=form.GetMain();
                  if(main!=NULL)
                     main.BringToTop();
                  form.BringToTop();                                    // form on the background - above all others
                  form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX()); // Cursor shift relative to the X coordinate
                  form.SetOffsetY(this.m_mouse.CoordY()-form.CoordY()); // Cursor shift relative to the Y coordinate
                  this.ResetAllInteractionExeptOne(form);               // Reset interaction flags for all forms except the current one
                  
                  //--- Get the maximum ZOrder
                  long zmax=this.GetZOrderMax();
                  //--- If the maximum ZOrder has been received and the form's ZOrder is less than the maximum one or the maximum ZOrder of all forms is equal to zero
                  if(zmax>WRONG_VALUE && (form.Zorder()<zmax || zmax==0))
                    {
                     //--- If the form is not a control point for managing an extended standard graphical object,
                     //--- set the form's ZOrder above all others
                     if(form.Type()!=OBJECT_DE_TYPE_GFORM_CONTROL)
                        this.SetZOrderMAX(form);
                    }
                 }
               form.OnMouseEvent(MOUSE_EVENT_INSIDE_ACTIVE_AREA_PRESSED,lparam,dparam,sparam);
               ::ChartRedraw(form.ChartID());
              }
            //+---------------------------------------------------------------------------------------------+


Außerdem fügen wir drei neue Blöcke hinzu, um neue Ereignisse des Mauszeigers innerhalb des Kontrollbereichs des Formularobjekts zu behandeln:

            //+--------------------------------------------------------------------------------------------------+
            //| 'The cursor is inside the window scrolling area, the mouse wheel is being scrolled' event handler|
            //+--------------------------------------------------------------------------------------------------+
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL)
              {
               form.OnMouseEvent(MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL,lparam,dparam,sparam);
              }
            //+-------------------------------------------------------------------------------------------------+
            //| 'The cursor is inside the control area, no mouse buttons are clicked' event handler             |
            //+-------------------------------------------------------------------------------------------------+
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_NOT_PRESSED)
              {
               form.OnMouseEvent(MOUSE_EVENT_INSIDE_CONTROL_AREA_NOT_PRESSED,lparam,dparam,sparam);
              }
            //+-------------------------------------------------------------------------------------------------+
            //| 'The cursor is inside the control area, a mouse button is clicked (any)' event handler          |
            //+-------------------------------------------------------------------------------------------------+
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED)
              {
               form.OnMouseEvent(MOUSE_EVENT_INSIDE_CONTROL_AREA_PRESSED,lparam,dparam,sparam);
              }
            //+-------------------------------------------------------------------------------------------------+
            //| 'The cursor is inside the control area, the mouse wheel is being scrolled' event handler        |
            //+-------------------------------------------------------------------------------------------------+
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL)
              {
               form.OnMouseEvent(MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL,lparam,dparam,sparam);
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

Von hier aus werden die Objekt-Ereignishandlungen aufgerufen, wenn sich der Cursor innerhalb des Kontrollbereichs befindet.

Alles ist bereit für einen Test. Überprüfen wir die Ergebnisse.


Test

Um den Test durchzuführen, verwende ich den EA aus dem vorherigen Artikel und speichere ihn in \MQL5\Experts\TestDoEasy\Part123\ als TestDoEasy123.mq5.

Die Verbesserungen sind minimal: Wir haben eine Makro-Substitution, um die Anzahl der erstellten Paneele anzugeben. Wir geben den Wert 1 ein, da vorerst nur eine Paneele erstellt wird:

//+------------------------------------------------------------------+
//|                                                     TstDE123.mq5 |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- defines
#define  FORMS_TOTAL (1)   // Number of created forms
#define  START_X     (4)   // Initial X coordinate of the shape
#define  START_Y     (4)   // Initial Y coordinate of the shape
#define  KEY_LEFT    (65)  // (A) Left
#define  KEY_RIGHT   (68)  // (D) Right
#define  KEY_UP      (87)  // (W) Up
#define  KEY_DOWN    (88)  // (X) Down
#define  KEY_FILL    (83)  // (S) Filling
#define  KEY_ORIGIN  (90)  // (Z) Default
#define  KEY_INDEX   (81)  // (Q) By index


In OnInit(), d.h. in der Schleife zur Erstellung der Paneels, fügen wir diese Makro-Substitution hinzu. Wir erhöhen die Breite der Trennlinie des SplitContainer-Steuerelements leicht um zwei Pixel, damit das gepunktete Rechteck besser aussieht.
Um Zeiger auf erstellte Formularobjekte abzurufen, wird die Methode zum Abrufen eines Zeigers aus einer Objektbeschreibung verwendet.
.
Die Objektbeschreibung wird beim Anlegen des Objekts angegeben
:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set EA global variables
   ArrayResize(array_clr,2);        // Array of gradient filling colors
   array_clr[0]=C'26,100,128';      // Original ≈Dark-azure color
   array_clr[1]=C'35,133,169';      // Lightened original color
//--- Create the array with the current symbol and set it to be used in the library
   string array[1]={Symbol()};
   engine.SetUsedSymbols(array);
   //--- Create the timeseries object for the current symbol and period, and show its description in the journal
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions

//--- Create the required number of WinForms Panel objects
   CPanel *pnl=NULL;
   for(int i=0;i<FORMS_TOTAL;i++)
     {
      pnl=engine.CreateWFPanel("WinForms Panel"+(string)i,(i==0 ? 50 : 70),(i==0 ? 50 : 70),410,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false);
      if(pnl!=NULL)
        {
         pnl.Hide();
         Print(DFUN,"Panel description: ",pnl.Description(),", Type and name: ",pnl.TypeElementDescription()," ",pnl.Name());
         //--- Set Padding to 4
         pnl.SetPaddingAll(3);
         //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs
         pnl.SetMovable(InpMovable);
         pnl.SetAutoSize(InpAutoSize,false);
         pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false);
   
         //--- Create TabControl
         pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,InpTabControlX,InpTabControlY,pnl.Width()-30,pnl.Height()-40,clrNONE,255,true,false);
         CTabControl *tc=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0);
         if(tc!=NULL)
           {
            tc.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode);
            tc.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment);
            tc.SetMultiline(InpTabCtrlMultiline);
            tc.SetHeaderPadding(6,0);
            tc.CreateTabPages(15,0,56,20,TextByLanguage("Вкладка","TabPage"));
            //--- Create a text label with a tab description on each tab
            for(int j=0;j<tc.TabPages();j++)
              {
               tc.CreateNewElement(j,GRAPH_ELEMENT_TYPE_WF_LABEL,322,120,80,20,clrDodgerBlue,255,true,false);
               CLabel *label=tc.GetTabElement(j,0);
               if(label==NULL)
                  continue;
               //--- If this is the very first tab, then there will be no text
               label.SetText(j<5 ? "" : "TabPage"+string(j+1));
              }
            for(int n=0;n<5;n++)
              {
               //--- Create a SplitContainer control on each tab
               tc.CreateNewElement(n,GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,10,10,tc.Width()-22,tc.GetTabField(0).Height()-22,clrNONE,255,true,false);
               //--- Get the SplitContainer control from each tab
               CSplitContainer *split_container=tc.GetTabElementByType(n,GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,0);
               if(split_container!=NULL)
                 {
                  //--- The separator will be vertical for each even tab and horizontal for each odd one
                  split_container.SetSplitterOrientation(n%2==0 ? CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL : CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL,true);
                  //--- The separator distance on each tab will be 50 pixels
                  split_container.SetSplitterDistance(50,true);
                  //--- The width of the separator on each subsequent tab will increase by 2 pixels
                  split_container.SetSplitterWidth(6+2*n,false);
                  //--- Make a fixed separator for the tab with index 2, and a movable one for the rest
                  split_container.SetSplitterFixed(n==2 ? true : false);
                  //--- For a tab with index 3, the second panel will be in a collapsed state (only the first one is visible)
                  if(n==3)
                     split_container.SetPanel2Collapsed(true);
                  //--- For a tab with index 4, the first panel will be in a collapsed state (only the second one is visible)
                  if(n==4)
                     split_container.SetPanel1Collapsed(true);
                  //--- On each of the control panels...
                  for(int j=0;j<2;j++)
                    {
                     CSplitContainerPanel *panel=split_container.GetPanel(j);
                     if(panel==NULL)
                        continue;
                     //--- ...create a text label with the panel name
                     if(split_container.CreateNewElement(j,GRAPH_ELEMENT_TYPE_WF_LABEL,4,4,panel.Width()-8,panel.Height()-8,clrDodgerBlue,255,true,false))
                       {
                        CLabel *label=split_container.GetPanelElementByType(j,GRAPH_ELEMENT_TYPE_WF_LABEL,0);
                        if(label==NULL)
                           continue;
                        label.SetTextAlign(ANCHOR_CENTER);
                        label.SetText(TextByLanguage("Панель","Panel")+string(j+1));
                       }
                    }
                 }
              }
           }
        }
     }
//--- Display and redraw all created panels
   for(int i=0;i<FORMS_TOTAL;i++)
     {
      pnl=engine.GetWFPanel("WinForms Panel"+(string)i);
      if(pnl!=NULL)
        {
         pnl.Show();
         pnl.Redraw(true);
        }
     }
        
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Kompilieren Sie den EA und starten Sie ihn auf einem Chart:


Wenn die ausgewählte Registerkarte über den Rand des Containers ragt und wenn das Bedienfeld verschoben wird, sind keine Artefakte mehr zu sehen. Der Abstand zwischen den Kopfzeilen und den Bildlaufsteuerelementen mit der vertikalen Kopfleiste ist jetzt kleiner und übersichtlicher.

Wenn sich die Kopfzeilen auf der rechten Seite befinden, ist die rechte Seite der Bedienfelder leicht abgeschnitten (obwohl wir das nicht sehen können). Dadurch gelangt der Cursor nicht zu den verdeckten Teilen der Felder, sodass wir die Registerkartenüberschriften problemlos handhaben können. Das Gleiche geschieht, wenn die Felder durch die Trennlinie verkleinert werden, das die Beschriftung des Feldes durch das Objekt der Klasse CLabel fast verdeckt. Der Cursor befindet sich physisch über den grafischen Beschriftungen, aber sie sind abgeschnitten und der Cursor befindet sich virtuell über ihrem unsichtbaren Bereich, und das Objekt wird nicht bearbeitet.
Die Trennlinie des SplitContainer-Steuerelements sieht jetzt besser aus, wenn man mit der Maus interagiert.


Was kommt als Nächstes?

Im nächsten Artikel werde ich die Entwicklung von TabControl fortsetzen.

Alle Dateien der aktuellen Bibliotheksversion, des Test-EA und des Chartereignis-Kontrollindikators für MQL5 sind unten angehängt, damit Sie sie testen und herunterladen können.

Zurück zum Inhalt

*Vorherige Artikel in dieser Reihe:

 
DoEasy. Steuerung (Teil 20): SplitContainer WinForms-Objekt
DoEasy. Steuerung (Teil 21): SplitContainer-Steuerung. Paneel-Trennlinie
DoEasy. Steuerung (Teil 22): SplitContainer. Ändern der Eigenschaften des erstellten Objekts



Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/11634

Beigefügte Dateien |
MQL5.zip (4482.83 KB)
Verwenden von Linien in MQL5 Verwenden von Linien in MQL5
In diesem Artikel erfahren Sie, wie Sie mit den wichtigsten Linien wie Trendlinien, Unterstützung und Widerstand von MQL5 verwenden können.
Einen Expert Advisor von Grund auf entwickeln (Teil 30): CHART TRADE als Indikator? Einen Expert Advisor von Grund auf entwickeln (Teil 30): CHART TRADE als Indikator?
Heute werden wir wieder Chart Trade verwenden, aber dieses Mal wird es ein On-Chart-Indikator sein, der auf dem Chart laufen kann oder auch nicht.
Techniken des MQL5-Assistenten, die Sie kennen sollten (Teil 04): Die Lineare Diskriminanzanalyse Techniken des MQL5-Assistenten, die Sie kennen sollten (Teil 04): Die Lineare Diskriminanzanalyse
Der Händler von heute ist ein Philomath, der fast immer (entweder bewusst oder unbewusst...) nach neuen Ideen sucht, sie ausprobiert, sich entscheidet, sie zu modifizieren oder zu verwerfen; ein explorativer Prozess, der einiges an Sorgfalt kosten sollte. Diese Artikelserie wird vorschlagen, dass der MQL5-Assistent eine Hauptstütze für Händler sein sollte.
Neuronale Netze leicht gemacht (Teil 30): Genetische Algorithmen Neuronale Netze leicht gemacht (Teil 30): Genetische Algorithmen
Heute möchte ich Ihnen eine etwas andere Lernmethode vorstellen. Wir können sagen, dass sie von Darwins Evolutionstheorie entlehnt ist. Sie ist wahrscheinlich weniger kontrollierbar als die zuvor besprochenen Methoden, aber sie ermöglicht die Ausbildung nicht-differenzierbarer Modelle.