English Русский 中文 Español 日本語 Português
preview
DoEasy. Steuerung (Teil 5): Basisobjekt von WinForms, Paneel-Steuerelement, Parameter AutoSize

DoEasy. Steuerung (Teil 5): Basisobjekt von WinForms, Paneel-Steuerelement, Parameter AutoSize

MetaTrader 5Beispiele | 6 Juli 2022, 10:18
108 0
Artyom Trishkin
Artyom Trishkin

Inhalt


Konzept

Bevor ich die Eigenschaften von AutoSize und AutoSizeMode des Paneel-Objekts implementiere, erstelle ich die Basisklasse aller Bibliotheksobjekte von WinForms. Da viele der Eigenschaften solcher Objekte voneinander vererbt werden, können die Eigenschaften des Paneels (engl. panel), an dem ich gerade arbeite, auch für andere WinForms-Objekte verwendet werden. Um zu vermeiden, dass für jedes der Objekte ähnliche Eigenschaften festgelegt werden, erstelle ich ein Basis-WinForms-Objekt, von dem alle anderen Objekte dieses Typs geerbt werden. Das Basisobjekt selbst wird von der Formularobjektklasse geerbt, in der die Interaktion mit der Maus implementiert wird.

Wenn für das im Paneel befindliche Objekt die Dock-Eigenschaft (die im vorherigen Artikel betrachtet wurde) aktiviert ist, „klebt“ ein solches Objekt an den Rändern seines Containers. Der Containerrahmen wird in der DockMode-Eigenschaft angegeben. Wenn in diesem Fall jedes nachfolgende Objekt, das innerhalb des Paneels platziert wird, die gleiche Bindung an die Seite seines Containers (Paneel) hat wie das vorherige Objekt, das innerhalb des Paneels platziert wurde, dann wird es an die nächste Seite des vorherigen Objekts und nicht an die gebunden angegebene Seite des Behälters. Somit werden alle Objekte, die innerhalb des Paneels platziert und beispielsweise an den linken Rand des Containers gebunden sind, in einer Reihe von links nach rechts angeordnet. Wenn das Paneel den AutoSize-Modus aktiviert hat, wird der Container automatisch in der Breite vergrößert, damit alle Objekte, die sich darin befinden und in einer Reihe angeordnet sind, nicht über ihren Container hinausragen. Genauso sollte sich der Container verhalten, wenn ein Objekt, das über die Kanten des Paneels hinausragt, darin platziert wird. Wenn für das Paneel der AutoSize-Modus aktiviert ist, muss es die Größe seiner Seiten anpassen, damit das Objekt seine Grenzen nicht überschreitet.

Gleichzeitig gibt es einen Unterschied, ob das Objekt an einer der Seiten seines Containers befestigt ist und der Container selbst die AutoSize-Eigenschaft aktiv hat oder nicht.
Alle Objekte, die in einem Container mit aktiver automatischer Größenanpassung platziert werden, werden in der Reihenfolge ihrer Priorität platziert, die durch die Seriennummer des Objekts in der Liste der an den Container angehängten Objekte angegeben ist. Dadurch können wir die Position von Objekten innerhalb des Containers vorbestimmen, dessen Größe sich automatisch an die Gesamtgröße aller daran gebundenen Elemente anpasst. Ich werde dies in den folgenden Artikeln berücksichtigen. Heute werde ich die Bibliotheksklassen gemäß der Aufgabe verbessern, ein neues Basis-WinForms-Objekt zu erstellen und die Autosize-Eigenschaft zu implementieren, wenn ein direkt daran angehängtes Element aus dem Paneel erstellt wird.

Lassen Sie uns zusätzlich zu den angegebenen Aufgaben eine kleine Optimierung der Konstruktion der grafischen Elemente innerhalb des Paneels durchführen. Da wir zuerst alle an das Paneel angehängten Elemente nach ihren Seriennummern und nach ihren Bindungswerten anordnen müssen (Dock) und dann die Größe des Paneels ändern müssen, falls die automatische Größenanpassung so eingestellt ist, dass sie zu seinem internen Inhalt passt (und wenn es wirklich notwendig), dann müssen wir zuerst alle Elemente „virtuell“ innerhalb des Paneels platzieren, dann sehen, wie sehr wir die Größe des Paneels ändern müssen, und es bei Bedarf ändern. Erst danach sollten wir alle Elemente innerhalb des Paneels neu zeichnen. Wenn wir die Elemente stattdessen sofort zeichnen, werden die Änderungen in ihrer Größe und ihrem Bindungspunkt in Echtzeit sichtbar und verursachen verschiedene Rendering-Artefakte rund um das Paneel. Daher werden wir zuerst die Elemente in der richtigen Reihenfolge anordnen, ihre Größe bei Bedarf ändern (dies hängt von der Methode zum Binden des Elements und seiner Nummer in der Liste der gebundenen Elemente ab) und dann berechnen, wie stark die Größe des Paneels geändert werden muss. Danach zeichnen wir das Paneel und die darin befindlichen Elemente erneut in einer neuen Reihenfolge.


Verbesserung des Bibliotheksklassen

Da ich hier ein neues Bibliotheksobjekt implementieren werde, muss ich seinen Typ zur Liste der Bibliotheksobjekttypen hinzufügen. Außerdem müssen wir den Objekttyp kennen, wenn wir auf das Paneel-Unterlagenobjekt zugreifen.
Geben wir in \MQL5\Include\DoEasy\Defines.mqh, nämlich in der Liste der Bibliotheksobjekttypen, den neuen Typ — Basis-WinForms-Objekt ein:

//+------------------------------------------------------------------+
//| List of library object types                                     |
//+------------------------------------------------------------------+
enum ENUM_OBJECT_DE_TYPE
  {
//--- Graphics
   OBJECT_DE_TYPE_GBASE =  COLLECTION_ID_LIST_END+1,              // "Base object of all library graphical objects" object type
   OBJECT_DE_TYPE_GELEMENT,                                       // "Graphical element" object type
   OBJECT_DE_TYPE_GFORM,                                          // Form object type
   OBJECT_DE_TYPE_GFORM_CONTROL,                                  // "Form for managing pivot points of graphical object" object type
   OBJECT_DE_TYPE_GSHADOW,                                        // Shadow object type
   //--- WinForms
   OBJECT_DE_TYPE_GWF_BASE,                                       // WinForms Base object type (base abstract WinForms object)
   OBJECT_DE_TYPE_GWF_PANEL,                                      // WinForms Panel object type
//--- Animation
   OBJECT_DE_TYPE_GFRAME,                                         // "Single animation frame" object type
//--- ...
//--- ...
  }


Fügen wir der Liste der grafischen Elementtypen zwei neue Typen hinzu – grafisches Unterlageelement und Basis-WinForms-Objekt. Benennen wir außerdem die Makroersetzung GRAPH_ELEMENT_TYPE_PANEL in GRAPH_ELEMENT_TYPE_WF_PANEL um:

//+------------------------------------------------------------------+
//| The list of graphical element types                              |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_STANDARD,                       // Standard graphical object
   GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED,              // Extended standard graphical object
   GRAPH_ELEMENT_TYPE_SHADOW_OBJ,                     // Shadow object
   GRAPH_ELEMENT_TYPE_ELEMENT,                        // Element
   GRAPH_ELEMENT_TYPE_FORM,                           // Form
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Window
   //--- WinForms
   GRAPH_ELEMENT_TYPE_WF_UNDERLAY,                    // Panel object underlay
   GRAPH_ELEMENT_TYPE_WF_BASE,                        // Windows Forms Base
   GRAPH_ELEMENT_TYPE_WF_PANEL,                       // Windows Forms Panel
  };
//+------------------------------------------------------------------+

Dieser Typ ermöglicht es uns zu wissen, welches Objekt gerade ausgewählt ist und was damit zu tun ist, wenn dies der notwendige Typ ist.

In \MQL5\Include\DoEasy\Data.mqh wurden neuen Nachrichtenindizes hinzugefügt:

   MSG_GRAPH_ELEMENT_TYPE_WINDOW,                     // Window
   MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY,                // Underlay of the Panel WinForms control object
   MSG_GRAPH_ELEMENT_TYPE_WF_BASE,                    // WinForms base control
   MSG_GRAPH_ELEMENT_TYPE_WF_PANEL,                   // Panel control

und die Textnachrichten, die den neu hinzugefügten Indizes entsprechen:

   {"Окно","Window"},
   {"Подложка объекта-элемента управления WinForms \"Панель\"","Underlay object-control WinForms \"Panel\""},
   {"Базовый элемент управления WinForms","Base WinForms control"},
   {"Элемент управления \"Panel\"","Control element \"Panel\""},


Alle grafischen Bibliotheksobjekte werden von der grafischen Basisobjektklasse der CGBaseObj-Bibliothek abgeleitet. Die Klasse enthält alle Basismethoden zum Handhaben beliebiger grafischer Bibliotheksobjekte. Fügen wir in \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh die Anzeige der Beschreibung von zwei neuen Arten von grafischen Bibliothekselementen zur Methode hinzu, die die Beschreibung eines Typs von grafischen Elementen zurückgibt:

//+------------------------------------------------------------------+
//| Return the description of the graphical element type             |
//+------------------------------------------------------------------+
string CGBaseObj::TypeElementDescription(void)
  {
   return
     (
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD)           :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)  :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_ELEMENT            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT)            :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_SHADOW_OBJ         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ)         :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM)               :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WINDOW             ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW)             :
      //---
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY)        :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_BASE            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE)            :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_PANEL           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL)           :
      "Unknown"
     );
  }
//+------------------------------------------------------------------+


Machen wir noch einige Methoden in der Klasse des grafischen Elementobjekts auf der Leinwand virtuell, von der die verbleibenden Canvas-Objekte der Bibliotheksgrafiken abgeleitet sind. Die Klasse befindet sich in \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh.
Die Implementierung einiger Methoden in der Klasse kann sich als ungeeignet für einige untergeordnete Objekte der Klasse herausstellen. Wenn wir sie virtuell machen, können wir diese Methoden in den abgeleiteten Klassen ändern, in denen sich die Implementierung der Methoden von der in der Klasse erstellten unterscheiden sollte.

//+------------------------------------------------------------------+
//| Methods of simplified access to object properties                |
//+------------------------------------------------------------------+
//--- Set the (1) X, (2) Y coordinates, (3) element width, (4) height, (5) right (6) and bottom edge,
   virtual bool      SetCoordX(const int coord_x);
   virtual bool      SetCoordY(const int coord_y);
   virtual bool      SetWidth(const int width);
   virtual bool      SetHeight(const int height);
   void              SetRightEdge(void)                        { this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge());           }
   void              SetBottomEdge(void)                       { this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge());         }

...

//+------------------------------------------------------------------+
//| The methods of filling, clearing and updating raster data        |
//+------------------------------------------------------------------+
//--- 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);
//--- Update the element
   void              Update(const bool redraw=false)           { this.m_canvas.Update(redraw);                                         }

Nun werden wir in den abgeleiteten Klassen, in denen eine andere Implementierung dieser Methoden benötigt wird, einfach genau die gleichen virtuellen Methoden schreiben, allerdings mit einer anderen Implementierung als der hier gezeigten. Genau diese virtuellen Methoden der abgeleiteten Klassen werden aufgerufen, wenn auf die virtuelle Methode der Elternklasse zugegriffen wird.

Ich muss einigen Methoden, die ich gerade verbessere, das Flag hinzufügen, das anzeigt, dass das grafische Objekt neu gezeichnet werden muss. Dadurch wird die Handhabung von Objektlisten optimiert, um das ständige Neuzeichnen jedes Objekts aus der Liste zu vermeiden, sondern zuerst alle Objekte in der Liste zu behandeln (z. B. die Größe jedes Objekts zu ändern und es an eine neue Position zu verschieben). Zeichnen wir nach Abschluss der Bearbeitung einer gesamten Liste einfach jedes Objekt mit seinen neuen Koordinaten oder mit seiner neuen Größe neu. Dies ist visuell schneller, als jedes Objekt in der Liste sofort nach dem Ändern seiner Abmessungen und Koordinaten neu zu zeichnen.

Ändern wir in der Klassendatei des Schattenobjekts \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqhdie Namen der Variablen und Methoden und entfernen das Wort „shadow““ (Schatten) aus ihnen. Ich glaube, das ist hier überflüssig.

Da wir den Schatten mit neuer Größe komplett neu zeichnen müssen, wenn auch mit den gleichen Schattenparametern, fügen wir noch eine weitere Variable, die den Schattenunschärferadius speichert, zwei Methoden für den Zugriff auf eine neue Variable sowie das Flag für die Notwendigkeit, das Ganze neu zu zeichnen Shadow-Objekt in die Methode zum Neuzeichnen der Schatten:

//+------------------------------------------------------------------+
//| Shadows object class                                             |
//+------------------------------------------------------------------+
class CShadowObj : public CGCnvElement
  {
private:
   color             m_color;                         // Shadow color
   uchar             m_opacity;                       // Shadow opacity
   uchar             m_blur;                          // Blur
//--- Gaussian blur
   bool              GaussianBlur(const uint radius);
//--- Return the array of weight ratios
   bool              GetQuadratureWeights(const double mu0,const int n,double &weights[]);
//--- Draw the object shadow form
   void              DrawShadowFigureRect(const int w,const int h);

public:
//--- Constructor
                     CShadowObj(const long chart_id,
                                const int subwindow,
                                const string name,
                                const int x,
                                const int y,
                                const int w,
                                const int h);

//--- Supported object properties (1) integer and (2) string ones
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)  { return true; }
   
//--- Draw an object shadow
   void              Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw);
   
//+------------------------------------------------------------------+
//| Methods of simplified access to object properties                |
//+------------------------------------------------------------------+
//--- (1) Set and (2) return the shadow color
   void              SetColor(const color colour)                             { this.m_color=colour;     }
   color             Color(void)                                        const { return this.m_color;     }
//--- (1) Set and (2) return the shadow opacity
   void              SetOpacity(const uchar opacity)                          { this.m_opacity=opacity;  }
   uchar             Opacity(void)                                      const { return this.m_opacity;   }
//--- (1) Set and (2) return the shadow blur
   void              SetBlur(const uchar blur)                                { this.m_blur=blur;        }
   uchar             Blur(void)                                         const { return this.m_blur;      }
  };
//+------------------------------------------------------------------+


Fügen wir im Klassenkonstruktor die standardmäßige Schattenopazität hinzu, die durch die Makroersetzung CLR_DEF_SHADOW_OPACITY in der Bibliotheksdatei Defines.mqh angegeben ist, und geben die standardmäßige Schattenunschärfe mit der Makroersetzung DEF_SHADOW_BLUR ebenfalls aus derselben Datei an:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CShadowObj::CShadowObj(const long chart_id,
                       const int subwindow,
                       const string name,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_SHADOW_OBJ,chart_id,subwindow,name,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GSHADOW; 
   CGCnvElement::SetColorBackground(clrNONE);
   CGCnvElement::SetOpacity(0);
   CGCnvElement::SetActive(false);
   this.m_opacity=CLR_DEF_SHADOW_OPACITY;
   this.m_blur=DEF_SHADOW_BLUR;
   color gray=CGCnvElement::ChangeColorSaturation(ChartColorBackground(),-100);
   this.m_color=CGCnvElement::ChangeColorLightness(gray,-50);
   this.m_shadow=false;
   this.m_visible=true;
   CGCnvElement::Erase();
  }
//+------------------------------------------------------------------+


Bei der Implementierung der Methode zum Zeichnen eines Schattenobjekts geben wir nun eindeutig das Flag zum Neuzeichnen des Schattens an. Anstelle der lokalen Variablen 'radius' werde ich die neue Variable m_blur verwenden. Dadurch können wir den Schattenunschärfewert für das spätere Neuzeichnen des Schattenobjekts mit den Parametern speichern, mit denen es ursprünglich gezeichnet wurde:

//+------------------------------------------------------------------+
//| Draw the object shadow                                           |
//+------------------------------------------------------------------+
void CShadowObj::Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw)
  {
//--- Set the shadow shift values to the variables by X and Y axes
   this.SetCoordXRelative(shift_x);
   this.SetCoordYRelative(shift_y);
//--- Calculate the height and width of the drawn rectangle
   int w=this.Width()-OUTER_AREA_SIZE*2;
   int h=this.Height()-OUTER_AREA_SIZE*2;
//--- Draw a filled rectangle with calculated dimensions
   this.DrawShadowFigureRect(w,h);
//--- Calculate the blur radius, which cannot exceed a quarter of the OUTER_AREA_SIZE constant
   this.m_blur=(blur_value>OUTER_AREA_SIZE/4 ? OUTER_AREA_SIZE/4 : blur_value);
//--- If failed to blur the shape, exit the method (GaussianBlur() displays the error on the journal)
   if(!this.GaussianBlur(this.m_blur))
      return;
//--- Shift the shadow object by X/Y offsets specified in the method arguments and update the canvas
   CGCnvElement::Move(this.CoordX()+this.CoordXRelative(),this.CoordY()+this.CoordYRelative(),redraw);
   CGCnvElement::Update(redraw);
  }
//+------------------------------------------------------------------+


Da ich das Basisobjekt aller Bibliotheksobjekte von WinForms etwas später implementieren werde, sollten einige Variablen und Methoden in den implementierten WinForms-Objektklassen und ihren übergeordneten Klassen entweder an eine neue Basisklasse oder an ihre übergeordnete Klasse — die CForm-Klasse — übergeben werden. Dadurch bleiben alle Variablen und Methoden auf den Ebenen der Klassen der Vererbungshierarchie verfügbar, wo sie benötigt werden.

Beispielsweise werden beim Erstellen eines Objekts seine Koordinaten und seine Größe in den Klassenvariablen von CPanel gespeichert. Wir benötigen die gleichen Daten in anderen WinForms-Objekten. Daher verschieben wir sie in die übergeordnete Klasse der WinForms-Objekte in \MQL5\Include\DoEasy\Objects\Graph\Form.mqh.
Auf den ersten Blick wäre es logisch, diese Variablen und Methoden in die Basisklasse von WinForms-Objekten zu verschieben. Die Daten können sich aber auch für Formularobjekte als nützlich erweisen, deren Klasse die übergeordnete Klasse für die Klasse des Basisobjekts aller WinForms-Objekte ist. Verschieben wir also diese Variablen dorthin:

//+------------------------------------------------------------------+
//| Form object class                                                |
//+------------------------------------------------------------------+
class CForm : public CGCnvElement
  {
private:
   CArrayObj         m_list_elements;                          // List of attached elements
   CAnimations      *m_animations;                             // Pointer to the animation object
   CShadowObj       *m_shadow_obj;                             // Pointer to the shadow object
   CMouseState       m_mouse;                                  // "Mouse status" class object
   ENUM_MOUSE_FORM_STATE m_mouse_form_state;                   // Mouse status relative to the form
   ushort            m_mouse_state_flags;                      // Mouse status flags
   color             m_color_frame;                            // Form frame color
   int               m_offset_x;                               // Offset of the X coordinate relative to the cursor
   int               m_offset_y;                               // Offset of the Y coordinate relative to the cursor
   int               m_init_x;                                 // Newly created form X coordinate
   int               m_init_y;                                 // Newly created form Y coordinate
   int               m_init_w;                                 // Newly created form width
   int               m_init_h;                                 // Newly created form height

//--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames
   void              ResetArrayFrameT(void);
   void              ResetArrayFrameQ(void);
   void              ResetArrayFrameG(void);
   
//--- Create a new graphical object

Lassen Sie uns die CreateNewElement()-Methode in zwei Teile teilen, um den Code zu optimieren – erstellen Sie noch eine weitere CreateAndAddNewElement()-Methode, bei der ein neues Element erstellt und der Liste hinzugefügt wird. Deklarieren wir die Methode im geschützten Abschnitt der Klasse. Verschieben wir die Methoden zum Umgang mit den Variablen, die die Anfangskoordinaten und die Objektgröße speichern, aus der CPanel-Klasse in den öffentlichen Abschnitt:

protected:
   CArrayObj         m_list_tmp;                               // List for storing the pointers
   int               m_frame_width_left;                       // Form frame width to the left
   int               m_frame_width_right;                      // Form frame width to the right
   int               m_frame_width_top;                        // Form frame width at the top
   int               m_frame_width_bottom;                     // Form frame width at the bottom
//--- Initialize the variables
   void              Initialize(void);
   void              Deinitialize(void);
//--- Create a shadow object
   void              CreateShadowObj(const color colour,const uchar opacity);
//--- Return the name of the dependent object
   string            CreateNameDependentObject(const string base_name)  const
                       { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name;   }
//--- Update coordinates of bound objects
   virtual bool      MoveDependentObj(const int x,const int y,const bool redraw=false);
//--- Create a new bound element and add it to the list of bound objects
   CGCnvElement     *CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                            CGCnvElement *main,
                                            const int x,
                                            const int y,
                                            const int w,
                                            const int h,
                                            const color colour,
                                            const uchar opacity,
                                            const bool activity);
   
public:
//--- Return the initial (1) X and (2) Y coordinate, (3) form width and (4) height
   int               GetCoordXInit(void)           const { return this.m_init_x;             }
   int               GetCoordYInit(void)           const { return this.m_init_y;             }
   int               GetWidthInit(void)            const { return this.m_init_w;             }
   int               GetHeightInit(void)           const { return this.m_init_h;             }
//--- Set the initial (1) X and (2) Y coordinate, (3) form width and (4) height
   void              SetCoordXInit(const int value)      { this.m_init_x=value;              }
   void              SetCoordYInit(const int value)      { this.m_init_y=value;              }
   void              SetWidthInit(const int value)       { this.m_init_w=value;              }
   void              SetHeightInit(const int value)      { this.m_init_h=value;              }
//--- Return (1) the mouse status relative to the form, as well as (2) X and (3) Y coordinate of the cursor
   ENUM_MOUSE_FORM_STATE MouseFormState(const int id,const long lparam,const double dparam,const string sparam);
   int               MouseCursorX(void)            const { return this.m_mouse.CoordX();     }
   int               MouseCursorY(void)            const { return this.m_mouse.CoordY();     }


Wir machen die Methode CreateNewElement() virtuell und fügen das Flag hinzu, das die Notwendigkeit angibt, ein neu erstelltes Objekt zu rendern:

//--- Create a new attached element
   virtual bool      CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                      CGCnvElement *main,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool activity,
                                      const bool redraw);
//--- Add a new attached element
   bool              AddNewElement(CGCnvElement *obj,const int x,const int y);

Die Methode wurde virtuell gemacht, damit wir dieselbe Methode mit ihrer eigenen Implementierung in jeder geerbten Klasse erstellen können. Das Flag zur Objekt-Neuzeichnung wird benötigt, um zu vermeiden, dass jedes Objekt direkt nach der Erstellung einzeln angezeigt wird. Stattdessen erstellen wir zuerst alle Objekte, passen dann die Größe des Paneels an, auf dem es erstellt wird, und rendern schließlich alles danach. Dies geht viel schneller, da wir visuelle Artefakte vermeiden können, die beim Anzeigen der geänderten Paneelgröße entstehen, während wir jedes nachfolgende Objekt aus allen daran angehängten und gleichzeitig in einer Schleife erstellten Objekten erstellen.


Deklarieren wir im Methodenblock für einen vereinfachten Zugriff auf die Objekteigenschaften zwei Methoden zur Behandlung der Eigenschaft „Schattenunschärfe“:

//+------------------------------------------------------------------+
//| Methods of simplified access to object properties                |
//+------------------------------------------------------------------+
//--- (1) Set and (2) get the form frame color
   void              SetColorFrame(const color colour)                        { this.m_color_frame=colour;     }
   color             ColorFrame(void)                                   const { return this.m_color_frame;     }
//--- (1) Set and (2) return the form shadow color
   void              SetColorShadow(const color colour);
   color             ColorShadow(void) const;
//--- (1) Set and (2) return the form shadow opacity
   void              SetOpacityShadow(const uchar opacity);
   uchar             OpacityShadow(void) const;
//--- (1) Set and (2) return the form shadow blur
   void              SetBlurShadow(const uchar blur);
   uchar             BlurShadow(void) const;

  };
//+------------------------------------------------------------------+


Fügen wir nach dem Aufrufen der Eigenschaftsinitialisierungsmethode den Aufruf der Methoden zur Initialisierung von Eigenschaften hinzu, die die anfängliche Größe und die Koordinaten des Objekts für alle parametrischen Konstruktoren speichern:

//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CForm::CForm(const long chart_id,
             const int subwindow,
             const string name,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,chart_id,subwindow,name,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GFORM; 
   this.Initialize();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+
//| Current chart constructor specifying the subwindow               |
//+------------------------------------------------------------------+
CForm::CForm(const int subwindow,
             const string name,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,::ChartID(),subwindow,name,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GFORM; 
   this.Initialize();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+
//| Constructor on the current chart in the main chart window        |
//+------------------------------------------------------------------+
CForm::CForm(const string name,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,::ChartID(),0,name,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GFORM; 
   this.Initialize();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+


Setzen wir in der Initialisierungsmethode selbst Nullen in diese Werte:

//+------------------------------------------------------------------+
//| Initialize the variables                                         |
//+------------------------------------------------------------------+
void CForm::Initialize(void)
  {
   this.m_list_elements.Clear();
   this.m_list_elements.Sort();
   this.m_list_tmp.Clear();
   this.m_list_tmp.Sort();
   this.m_shadow_obj=NULL;
   this.m_shadow=false;
   this.m_frame_width_right=DEF_FRAME_WIDTH_SIZE;
   this.m_frame_width_left=DEF_FRAME_WIDTH_SIZE;
   this.m_frame_width_top=DEF_FRAME_WIDTH_SIZE;
   this.m_frame_width_bottom=DEF_FRAME_WIDTH_SIZE;
   this.m_gradient_v=true;
   this.m_gradient_c=false;
   this.m_mouse_state_flags=0;
   this.m_offset_x=0;
   this.m_offset_y=0;
   this.m_init_x=0;
   this.m_init_y=0;
   this.m_init_w=0;
   this.m_init_h=0;
   CGCnvElement::SetInteraction(false);
   this.m_animations=new CAnimations(CGCnvElement::GetObject());
   this.m_list_tmp.Add(m_animations);
  }
//+------------------------------------------------------------------+


Die Methode, die ein neues angehängtes Element erstellt und es zur Liste der angehängten Objekte hinzufügt:

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//| and add it to the list of bound objects                          |
//+------------------------------------------------------------------+
CGCnvElement *CForm::CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                            CGCnvElement *main,
                                            const int x,
                                            const int y,
                                            const int w,
                                            const int h,
                                            const color colour,
                                            const uchar opacity,
                                            const bool activity)
  {
//--- If the type of a created graphical element is less than the "element", inform of that and return 'false'
   if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),19));
      return NULL;
     }
//--- Specify the element index in the list
   int num=this.m_list_elements.Total();
//--- Create a graphical element name
   string ns=(::StringLen((string)num)<2 ? ::IntegerToString(num,2,'0') : (string)num);
   string name="Elm"+ns;
//--- Get the screen coordinates of the object relative to the coordinate system of the base object
   int elm_x=x;
   int elm_y=y;
   this.GetCoords(elm_x,elm_y);
//--- Create a new graphical element
   CGCnvElement *obj=this.CreateNewGObject(element_type,num,name,elm_x,elm_y,w,h,colour,opacity,false,activity);
   if(obj==NULL)
      return NULL;
//--- and add it to the list of bound graphical elements
   if(!this.AddNewElement(obj,elm_x,elm_y))
     {
      delete obj;
      return NULL;
     }
//--- Set the minimum properties for a bound graphical element
   obj.SetColorBackground(colour);
   obj.SetOpacity(opacity);
   obj.SetActive(activity);
   obj.SetMain(main);
   obj.SetBase(this.GetObject());
   obj.SetID(this.ID());
   obj.SetNumber(num);
   obj.SetCoordXRelative(x);
   obj.SetCoordYRelative(y);
   obj.SetZorder(this.Zorder(),false);
   obj.SetCoordXRelativeInit(x);
   obj.SetCoordYRelativeInit(y);
   return obj;
  }
//+------------------------------------------------------------------+

Die Methodenlogik wiederholt im Wesentlichen die Methodenlogik von CreateNewElement(), obwohl es keine bedingungslose Wiedergabe des erstellten Elements gibt. Die Methode gibt einfach den Zeiger auf ein erfolgreich erstelltes grafisches Element oder NULL zurück, falls beim Erstellen oder Hinzufügen des Elements zur Liste ein Fehler aufgetreten ist. Außerdem habe ich hier einen kleinen Fehler behoben. Die Liste der gebundenen Objekte enthält jetzt einen Objektindex für jedes Objekt.

Die Methode, die ein neues angehängtes Element erstellt, sieht jetzt anders aus – der gesamte Code zum Hinzufügen eines neuen Objekts und Hinzufügen zur Liste wurde in die oben betrachtete Methode verschoben und hier aufgerufen. Wenn das Objekt nicht erstellt oder nicht zur Liste hinzugefügt wird, gibt die Methode false zurück, andernfalls wird das Objekt mit dem Flag gerendert, das die Notwendigkeit seiner physischen Wiedergabe angibt, und die Methode gibt true zurück.

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CForm::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                             CGCnvElement *main,
                             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,main,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.Erase(colour,opacity,redraw);
   return true;
  }
//+------------------------------------------------------------------+


In der Methode zum Zeichnen des Schattens übergeben wir nun das Flag, das die Notwendigkeit anzeigt, den Schatten zu zeichnen, in der Draw()-Methode der Schattenobjektklasse:

//+------------------------------------------------------------------+
//| Draw the shadow                                                  |
//+------------------------------------------------------------------+
void CForm::DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=DEF_SHADOW_BLUR)
  {
//--- If the shadow flag is disabled, exit
   if(!this.m_shadow)
      return;
//--- If there is no shadow object, create it
   if(this.m_shadow_obj==NULL)
      this.CreateShadowObj(colour,opacity);
//--- If the shadow object exists, draw the shadow on it,
//--- set the shadow object visibility flag and
//--- move the form object to the foreground
   if(this.m_shadow_obj!=NULL)
     {
      this.m_shadow_obj.Draw(shift_x,shift_y,blur,true);
      this.m_shadow_obj.SetVisible(true,false);
      this.BringToTop();
     }
  }
//+------------------------------------------------------------------+

Hier geben wir immer true zurück, um den Schatten zu zeichnen. Falls der Schatten nicht benötigt wird, wird dies in den aufrufenden Methoden durch einen bedingten Operator überprüft. Dies ist einfacher, als das Rendern eines nicht unscharfen Schattenrechtecks zu steuern, um zu kontrollieren, ob es gezeichnet wird oder nicht, bevor die Unschärfe dieses Rechtecks verwendet wird, sowie andere damit verbundene Fallstricke. Stattdessen ist es viel einfacher, einfach zu prüfen, ob der Schatten gerendert werden muss, und die Methode aufzurufen.


Die Methode zum Festlegen der Formschattenunschärfe:

//+------------------------------------------------------------------+
//| Set the form shadow blur                                         |
//+------------------------------------------------------------------+
void CForm::SetBlurShadow(const uchar blur)
  {
   if(this.m_shadow_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT));
      return;
     }
   this.m_shadow_obj.SetBlur(blur);
  }
//+------------------------------------------------------------------+

Wenn kein Schattenobjekt vorhanden ist, zeigen wir die Meldung an, die angibt, dass das Schattenobjekt zuerst erstellt werden soll. Andernfalls wird das Verfahren zum Einstellen des Unschärfebetrags in das Schattenobjekt aufgerufen.

Die Methode, die die Schattenunschärfe des Formulars zurückgibt:

//+------------------------------------------------------------------+
//| Return the form shadow blur                                      |
//+------------------------------------------------------------------+
uchar CForm::BlurShadow(void) const
  {
   if(this.m_shadow_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT));
      return 0;
     }
   return this.m_shadow_obj.Blur();
  }
//+------------------------------------------------------------------+

Wenn kein Schattenobjekt vorhanden ist, zeigen wir die Meldung an, die angibt, dass das Schattenobjekt zuerst erstellt werden soll. Andernfalls wird die Methode zum Festlegen des Unschärfebetrags des Schattenobjekts zurückgegeben.

Abgesehen von den oben erwähnten Änderungen hat die Klasse einige kleinere Verbesserungen erhalten, die ihre Logik nicht beeinflussen. Es hat keinen Sinn, sie hier zu beschreiben. Sie können alle Änderungen in den unten angehängten Dateien einsehen.


Basisobjektklasse von WinForms

Erstellen wir in \MQL5\Include\DoEasy\Objects\Graph\WForms\eine neue Datei WinFormBase.mqh der Klasse CWinFormBase. Die Klasse sollte von der CForm-Formularobjektklasse geerbt werden. Damit die Basisklasse in der Datei sichtbar ist, fügen wir ihr die Klassendatei des Formularobjekts hinzu:

//+------------------------------------------------------------------+
//|                                                  WinFormBase.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\Form.mqh"
//+------------------------------------------------------------------+
//| Form object class                                                |
//+------------------------------------------------------------------+
class CWinFormBase : public CForm
  {
  }

Wir verschieben die Variablen und Methoden zu ihrer Handhabung aus der Objektklassendatei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh von Paneel WinForms in diese Klasse. Es soll alle Variablen und Methoden speichern, die wir für das Paneel-Objekt implementiert haben. Natürlich werden auch die neuen hinzugefügt.

Platzieren wir die folgenden Variablen im geschützten Abschnitt der Klasse:

//+------------------------------------------------------------------+
//| Form object class                                                |
//+------------------------------------------------------------------+
class CWinFormBase : public CForm
  {
protected:
   color             m_fore_color;                                   // Default text color for all control objects
   ENUM_FW_TYPE      m_bold_type;                                    // Font width type
   ENUM_FRAME_STYLE  m_border_style;                                 // Control frame style
   bool              m_autosize;                                     // Flag of the element auto resizing depending on the content
   ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode;                          // Mode of binding control borders to the container
   int               m_margin[4];                                    // Array of gaps of all sides between the fields of the current and adjacent controls
   int               m_padding[4];                                   // Array of gaps of all sides inside controls

private:

Im privaten Abschnitt platzieren wir die Methode, die die Schriftart-Flags zurückgibt:

private:
//--- Return the font flags
   uint              GetFontFlags(void);

public:

deklarieren wir im öffentlichen Abschnitt die virtuellen Methoden zum Löschen des Elements, Neuzeichnen und Ändern der Größe sowie der Klassenkonstruktoren und verschieben solche Methoden aus der CPanel-Klassendatei:

public:
//--- 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);
//--- Redraw the object
   virtual void      Redraw(bool redraw);
//--- Set the new size for the (1) current object and (2) the object specified by index
   virtual bool      Resize(const int w,const int h,const bool redraw);
   virtual bool      Resize(const int index,const int w,const int h,const bool redraw);

//--- Constructors
                     CWinFormBase(const long chart_id,
                                  const int subwindow,
                                  const string name,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
                     CWinFormBase(const string name) : CForm(::ChartID(),0,name,0,0,0,0)
                       {
                        this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
                       }
                       
//--- (1) Set and (2) return the default text color of all panel objects
   void              SetForeColor(const color clr)                   { this.m_fore_color=clr;               }
   color             ForeColor(void)                           const { return this.m_fore_color;            }

//--- (1) Set and (2) return the Bold font flag
   void              SetBold(const bool flag);
   bool              Bold(void);
//--- (1) Set and (2) return the Italic font flag
   void              SetItalic(const bool flag);
   bool              Italic(void);
//--- (1) Set and (2) return the Strikeout font flag
   void              SetStrikeout(const bool flag);
   bool              Strikeout(void);
//--- (1) Set and (2) return the Underline font flag
   void              SetUnderline(const bool flag);
   bool              Underline(void);
//--- (1) Set and (2) return the font style
   void              SetFontDrawStyle(ENUM_FONT_STYLE style);
   ENUM_FONT_STYLE   FontDrawStyle(void);
//--- (1) Set and (2) return the font width type
   void              SetFontBoldType(ENUM_FW_TYPE type);
   ENUM_FW_TYPE      FontBoldType(void)                        const { return this.m_bold_type;             }
//--- (1) Set and (2) return the frame style
   void              SetBorderStyle(const ENUM_FRAME_STYLE style)    { this.m_border_style=style;           }
   ENUM_FRAME_STYLE  BorderStyle(void)                         const { return this.m_border_style;          }

//--- (1) Set and (2) return the flag of the element auto resizing depending on the content
   virtual void      SetAutoSize(const bool flag,const bool redraw)  { this.m_autosize=flag;                }
   bool              AutoSize(void)                                  { return this.m_autosize;              }
   
//--- (1) Set and (2) return the mode of binding element borders to the container
   virtual void      SetDockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode,const bool redraw)
                       {
                        this.m_dock_mode=mode;
                       }
   ENUM_CANV_ELEMENT_DOCK_MODE DockMode(void)                  const { return this.m_dock_mode;             }
   
//--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides between the fields of this and another control
   void              SetMarginLeft(const int value)                  { this.m_margin[0]=value;              }
   void              SetMarginTop(const int value)                   { this.m_margin[1]=value;              }
   void              SetMarginRight(const int value)                 { this.m_margin[2]=value;              }
   void              SetMarginBottom(const int value)                { this.m_margin[3]=value;              }
   void              SetMarginAll(const int value)
                       {
                        this.SetMarginLeft(value); this.SetMarginTop(value); this.SetMarginRight(value); this.SetMarginBottom(value);
                       }
//--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields of this and another control
   int               MarginLeft(void)                          const { return this.m_margin[0];             }
   int               MarginTop(void)                           const { return this.m_margin[1];             }
   int               MarginRight(void)                         const { return this.m_margin[2];             }
   int               MarginBottom(void)                        const { return this.m_margin[3];             }

//--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control
   virtual void      SetPaddingLeft(const uint value)
                       {
                        this.m_padding[0]=((int)value<this.m_frame_width_left ? this.m_frame_width_left : (int)value);
                       }
   virtual void      SetPaddingTop(const uint value)
                       {
                        this.m_padding[1]=((int)value<this.m_frame_width_top ? this.m_frame_width_top : (int)value);
                       }
   virtual void      SetPaddingRight(const uint value)
                       {
                        this.m_padding[2]=((int)value<this.m_frame_width_right ? this.m_frame_width_right : (int)value);
                       }
   virtual void      SetPaddingBottom(const uint value)
                       {
                        this.m_padding[3]=((int)value<this.m_frame_width_bottom ? this.m_frame_width_bottom : (int)value);
                       }
   virtual void      SetPaddingAll(const uint value)
                       {
                        this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value);
                       }
   
//--- Set the width of the element frame (1) to the left, (2) at the top, (3) to the right and (4) at the bottom
   virtual void      SetFrameWidthLeft(const uint value)             { this.m_frame_width_left=(int)value;  }
   virtual void      SetFrameWidthTop(const uint value)              { this.m_frame_width_top=(int)value;   }
   virtual void      SetFrameWidthRight(const uint value)            { this.m_frame_width_right=(int)value; }
   virtual void      SetFrameWidthBottom(const uint value)           { this.m_frame_width_bottom=(int)value;}
   virtual void      SetFrameWidthAll(const uint value)
                       {
                        this.SetFrameWidthLeft(value); this.SetFrameWidthTop(value); this.SetFrameWidthRight(value); this.SetFrameWidthBottom(value);
                       }
   
//--- Return the width of the element frame (1) to the left, (2) at the top, (3) to the right and (4) at the bottom
   int               FrameWidthLeft(void)                      const { return this.m_frame_width_left;      }
   int               FrameWidthTop(void)                       const { return this.m_frame_width_top;       }
   int               FrameWidthRight(void)                     const { return this.m_frame_width_right;     }
   int               FrameWidthBottom(void)                    const { return this.m_frame_width_bottom;    }
   
//--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields inside the control
   int               PaddingLeft(void)                         const { return this.m_padding[0];            }
   int               PaddingTop(void)                          const { return this.m_padding[1];            }
   int               PaddingRight(void)                        const { return this.m_padding[2];            }
   int               PaddingBottom(void)                       const { return this.m_padding[3];            }
   
  };
//+------------------------------------------------------------------+

Die meisten übertragenen Methoden werden virtuell gemacht, damit sie bei Bedarf in geerbten Klassen umdefiniert werden können. Alle Methoden zum Setzen der Eigenschaften weisen nun das Präfix „Set“ in ihren Namen auf. Dies zeigt eindeutig die Methodenzugehörigkeit an.

Im parametrischen Konstruktor legen wir die grafischen Element- und Bibliotheksobjekttypen sowie die Standardeigenschaftswerte oder die in den Parametern übergebenen Werte fest:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CWinFormBase::CWinFormBase(const long chart_id,
                           const int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CForm(chart_id,subwindow,name,x,y,w,h)
  {
//--- Set the graphical element and library object types as a base WinForms object
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE);
   this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
//--- Initialize all variables
   this.m_fore_color=CLR_DEF_FORE_COLOR;
   this.m_bold_type=FW_TYPE_NORMAL;
   this.SetMarginAll(0);
   this.SetPaddingAll(0);
   this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetAutoSize(false,false);
   CForm::SetCoordXInit(x);
   CForm::SetCoordYInit(y);
   CForm::SetWidthInit(w);
   CForm::SetHeightInit(h);
   this.m_shadow=false;
   this.m_frame_width_right=0;
   this.m_frame_width_left=0;
   this.m_frame_width_top=0;
   this.m_frame_width_bottom=0;
   this.m_gradient_v=true;
   this.m_gradient_c=false;
  }
//+------------------------------------------------------------------+

Die Methoden wurden aus der CPanel-Klasse verschoben:

//+------------------------------------------------------------------+
//| Return the font flags                                            |
//+------------------------------------------------------------------+
uint CWinFormBase::GetFontFlags(void)
  {
   string name;
   int size;
   uint flags;
   uint angle;
   CGCnvElement::GetFont(name,size,flags,angle);
   return flags;
  }
//+------------------------------------------------------------------+
//| Set the Bold font flag                                           |
//+------------------------------------------------------------------+
void CWinFormBase::SetBold(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
     {
      this.m_bold_type=FW_TYPE_BOLD;
      CGCnvElement::SetFontFlags(flags | FW_BOLD);
     }
   else
      this.m_bold_type=FW_TYPE_NORMAL;
  }
//+------------------------------------------------------------------+
//| Return the Bold font flag                                        |
//+------------------------------------------------------------------+
bool CWinFormBase::Bold(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FW_BOLD)==FW_BOLD;
  }
//+------------------------------------------------------------------+
//| Set the Italic font flag                                         |
//+------------------------------------------------------------------+
void CWinFormBase::SetItalic(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
      CGCnvElement::SetFontFlags(flags | FONT_ITALIC);
  }
//+------------------------------------------------------------------+
//| Return the Italic font flag                                      |
//+------------------------------------------------------------------+
bool CWinFormBase::Italic(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FONT_ITALIC)==FONT_ITALIC;
  }
//+------------------------------------------------------------------+
//| Set the Strikeout font flag                                      |
//+------------------------------------------------------------------+
void CWinFormBase::SetStrikeout(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
      CGCnvElement::SetFontFlags(flags | FONT_STRIKEOUT);
  }
//+------------------------------------------------------------------+
//| Return the Strikeout font flag                                   |
//+------------------------------------------------------------------+
bool CWinFormBase::Strikeout(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FONT_STRIKEOUT)==FONT_STRIKEOUT;
  }
//+------------------------------------------------------------------+
//| Set the Underline font flag                                      |
//+------------------------------------------------------------------+
void CWinFormBase::SetUnderline(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
      CGCnvElement::SetFontFlags(flags | FONT_UNDERLINE);
  }
//+------------------------------------------------------------------+
//| Return the Underline font flag                                   |
//+------------------------------------------------------------------+
bool CWinFormBase::Underline(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FONT_UNDERLINE)==FONT_UNDERLINE;
  }
//+------------------------------------------------------------------+
//| Set the font style                                               |
//+------------------------------------------------------------------+
void CWinFormBase::SetFontDrawStyle(ENUM_FONT_STYLE style)
  {
   switch(style)
     {
      case FONT_STYLE_ITALIC     :  this.SetItalic(true);      break;
      case FONT_STYLE_UNDERLINE  :  this.SetUnderline(true);   break;
      case FONT_STYLE_STRIKEOUT  :  this.SetStrikeout(true);   break;
      default: break;
     }
  }
//+------------------------------------------------------------------+
//| Return the font style                                            |
//+------------------------------------------------------------------+
ENUM_FONT_STYLE CWinFormBase::FontDrawStyle(void)
  {
   return
     (
      this.Italic()     ?  FONT_STYLE_ITALIC    :
      this.Underline()  ?  FONT_STYLE_UNDERLINE :
      this.Strikeout()  ?  FONT_STYLE_UNDERLINE :
      FONT_STYLE_NORMAL
     );
  }
//+------------------------------------------------------------------+
//| Set the font width type                                          |
//+------------------------------------------------------------------+
void CWinFormBase::SetFontBoldType(ENUM_FW_TYPE type)
  {
   this.m_bold_type=type;
   uint flags=this.GetFontFlags();
   switch(type)
     {
      case FW_TYPE_DONTCARE   : CGCnvElement::SetFontFlags(flags | FW_DONTCARE);    break;
      case FW_TYPE_THIN       : CGCnvElement::SetFontFlags(flags | FW_THIN);        break;
      case FW_TYPE_EXTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_EXTRALIGHT);  break;
      case FW_TYPE_ULTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_ULTRALIGHT);  break;
      case FW_TYPE_LIGHT      : CGCnvElement::SetFontFlags(flags | FW_LIGHT);       break;
      case FW_TYPE_REGULAR    : CGCnvElement::SetFontFlags(flags | FW_REGULAR);     break;
      case FW_TYPE_MEDIUM     : CGCnvElement::SetFontFlags(flags | FW_MEDIUM);      break;
      case FW_TYPE_SEMIBOLD   : CGCnvElement::SetFontFlags(flags | FW_SEMIBOLD);    break;
      case FW_TYPE_DEMIBOLD   : CGCnvElement::SetFontFlags(flags | FW_DEMIBOLD);    break;
      case FW_TYPE_BOLD       : CGCnvElement::SetFontFlags(flags | FW_BOLD);        break;
      case FW_TYPE_EXTRABOLD  : CGCnvElement::SetFontFlags(flags | FW_EXTRABOLD);   break;
      case FW_TYPE_ULTRABOLD  : CGCnvElement::SetFontFlags(flags | FW_ULTRABOLD);   break;
      case FW_TYPE_HEAVY      : CGCnvElement::SetFontFlags(flags | FW_HEAVY);       break;
      case FW_TYPE_BLACK      : CGCnvElement::SetFontFlags(flags | FW_BLACK);       break;
      default                 : CGCnvElement::SetFontFlags(flags | FW_NORMAL);      break;
     }
  }
//+------------------------------------------------------------------+

Ich habe alle diese Methoden in den vorherigen Artikeln zum Erstellen des Paneel-Objekts betrachtet.


Die virtuellen Methoden zum Löschen des Elements (Füllung mit Farbe):

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CWinFormBase::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
//--- Fill the element having the specified color and the redrawing flag
   CGCnvElement::Erase(colour,opacity,redraw);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw)
      this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle());
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CWinFormBase::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false)
  {
//--- Fill the element having the specified color array and the redrawing flag
   CGCnvElement::Erase(colors,opacity,vgradient,cycle,redraw);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw)
      this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle());
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element completely                                     |
//+------------------------------------------------------------------+
void CWinFormBase::Erase(const bool redraw=false)
  {
//--- Fully clear the element with the redrawing flag
   CGCnvElement::Erase(redraw);
  }
//+------------------------------------------------------------------+

Diese virtuellen Methoden überschreiben dieselben Methoden der übergeordneten Klasse. Hier wird zuerst die Elternklasse aufgerufen, um das Element mit Farbe zu füllen, das Objektrahmen-Flag wird dann geprüft. Wenn der Rahmen vorhanden sein sollte und das Flag zur Objekt-Neuzeichnung gesetzt ist, wird der Rahmen gezeichnet. Als nächstes wird das Objekt aktualisiert.


Die Methode zum Festlegen der neuen Größe für das aktuelle Objekt:

//+------------------------------------------------------------------+
//| Set the new size for the current object                          |
//+------------------------------------------------------------------+
bool CWinFormBase::Resize(const int w,const int h,const bool redraw)
  {
//--- If the object width and height are equal to the passed ones, return 'true'
   if(this.Width()==w && this.Height()==h)
      return true;
//--- Declare the variable with the property change result
   bool res=true;
//--- Save the panel initial size
   int prev_w=this.Width();
   int prev_h=this.Height();
//--- Set the property change result to the 'res' variable
//--- (if the property value is not equal to the passed value)
   if(this.Width()!=w)
      res &=this.SetWidth(w);
   if(this.Height()!=h)
      res &=this.SetHeight(h);
   if(!res)
      return false;
//--- Calculate the value, by which the size should be changed
   int excess_w=this.Width()-prev_w;
   int excess_h=this.Height()-prev_h;
//--- Get the "Shadow" object
   CShadowObj *shadow=this.GetShadowObj();
//--- If the object has a shadow and the "Shadow" object has been received,
   if(this.IsShadow() && shadow!=NULL)
     {
      //--- save shadow shifts by X and Y,
      int x=shadow.CoordXRelative();
      int y=shadow.CoordYRelative();
      //--- set the shadow new width and height
      res &=shadow.SetWidth(shadow.Width()+excess_w);
      res &=shadow.SetHeight(shadow.Height()+excess_h);
      if(!res)
         return false;
      //--- If there is no need to redraw, remove the shadow
      if(!redraw)
         shadow.Erase();
      //--- Save the previously set shadow shift values relative to the panel
      shadow.SetCoordXRelative(x);
      shadow.SetCoordYRelative(y);
     }
//--- Redraw the element with new size
   if(redraw)
      this.Redraw(true);
//--- All is successful - return 'true'
   return true;
  }
//+------------------------------------------------------------------+

Die Methodenlogik wurde in den Codekommentaren ausführlich beschrieben. Kurz gesagt, wenn die vorhandene Objektgröße an die Methode übergeben wird, besteht keine Notwendigkeit, etwas zu ändern – geben Sie sofort true zurück. Wenn die an die Methode übergebene Größe nicht mit der Größe des Objekts übereinstimmt, ändern wir sie. Wenn es einen Schatten gibt, ändern wir seine Größe und zeichnen das gesamte Objekt neu, wenn das Neuzeichnen-Flag gesetzt ist. Mit anderen Worten, wenn das Neuzeichnen-Flag nicht gesetzt ist, ändert sich die Objektgröße, während das Objekt selbst nicht neu gezeichnet wird. Dies ist erforderlich, um das Neuzeichnen mehrerer Objekte zu beschleunigen, die an ein anderes Objekt gebunden sind. Nachdem die Größe aller gebundenen Objekte geändert wurde, sollten sie neu gezeichnet werden.

Die Methode, die die neue Größe für das durch den Index angegebene Objekt festlegt:

//+------------------------------------------------------------------+
//| Set the new size for the object specified by object index        |
//+------------------------------------------------------------------+
bool CWinFormBase::Resize(const int index,const int w,const int h,const bool redraw)
  {
   CWinFormBase *obj=this.GetElement(index);
   return(obj!=NULL ? obj.Resize(w,h,redraw) : false);
  }
//+------------------------------------------------------------------+

Die Methode erhält den Objektindex in der Liste der gebundenen Objekte, deren Größe geändert werden muss (die Größe wird auch an die Methode übergeben).
Wir holen uns das Objekt gemäß seines Index in der Liste und geben das Ergebnis des Aufrufs seiner oben betrachteten Methode Resize() zurück.


Die Methode zum Neuzeichnen eines Objekts:

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CWinFormBase::Redraw(bool redraw)
  {
//--- If the object type is less than the "Base WinForms object", exit
   if(this.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE)
      return;
//--- Get the "Shadow" object
   CShadowObj *shadow=this.GetShadowObj();
//--- If the object has a shadow and the "Shadow" object exists, redraw it
   if(this.IsShadow() && shadow!=NULL)
     {
      //--- remove the previously drawn shadow,
      shadow.Erase();
      //--- save the relative shadow coordinates,
      int x=shadow.CoordXRelative();
      int y=shadow.CoordYRelative();
      //--- redraw the shadow,
      if(redraw)
         shadow.Draw(0,0,shadow.Blur(),redraw);
      //--- restore relative shadow coordinates
      shadow.SetCoordXRelative(x);
      shadow.SetCoordYRelative(y);
     }
//--- If the redraw flag is set,
   if(redraw)
     {
      //--- completely redraw the object and save its new initial look
      this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c,redraw);
      this.Done();
     }
//--- otherwise, remove the object
   else
      this.Erase();
//--- Redraw all bound objects with the redraw flag
   for(int i=0;i<this.ElementsTotal();i++)
     {
      CWinFormBase *element=this.GetElement(i);
      if(element==NULL)
         continue;
      element.Redraw(redraw);
     }
//--- If the redraw flag is set and if this is the main object the rest are bound to,
//--- redraw the chart to display changes immediately
   if(redraw && this.GetMain()==NULL)
      ::ChartRedraw(this.ChartID());
  }
//+------------------------------------------------------------------+

Die Methodenlogik ähnelt der Logik der Methode zum Ändern der Objektgröße. Zuerst zeichnen wir das Schattenobjekt neu, da es niedriger sein sollte als andere Objekte. Als Nächstes, wenn das Neuzeichnen-Flag gesetzt ist, zeichnen wir das Objekt vollständig neu und legen sein aktuelles Erscheinungsbild als „Referenz“ fest. Wenn das Neuzeichnen-Flag nicht gesetzt ist, entfernen wir das gezeichnete Schattenobjekt und das Objekt selbst. Als Nächstes gehen wir durch die Liste der angehängten Objekte und zeichnen jedes von ihnen gemäß dem Neuzeichen-Flag neu — entweder entfernen oder alles zeichnen.
Schließlich aktualisieren wir das Chart für die sofortige Anzeige der implementierten Änderungen, falls das Neuzeichnen-Flag gesetzt ist und das Flag das Hauptobjekt in der gesamten Hierarchie der gebundenen Objekte ist (es hat kein Hauptobjekt und seine GetMain() -Methode gibt NULL zurück).

Die Basisklasse aller Bibliotheks-WinForms-Objekte ist fertig.

Verbessern wir die Paneel-Objektklasse in \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh.

Wenn die Dock-Eigenschaft für die an das Paneel gebundenen Objekte gesetzt ist, sollten diese Objekte automatisch in der Reihenfolge ihrer Indizes in der Liste der gebundenen Objekte platziert und gemäß den Regeln für die Bindungsart festgelegt werden. Mit anderen Worten, wenn sich Dock auf der linken Seite befindet, wird das Objekt in der Höhe auf die gesamte Höhe des Paneels gestreckt, an dem es befestigt ist, während seine linke Koordinate die linke Kante einer der Paneel-Unterlagen ist (wenn das Objekt das erste ist eines in der Liste) oder der rechte Rand des vorherigen Objekts aus der Liste der gebundenen Objekte. Diese Regel wird auf jedes Objekt aus der Liste angewendet. Außerdem sollten wir auch das Flag für die automatische Größenänderung des Bedienfelds und den Änderungsmodus für die Größenänderung berücksichtigen — nur vergrößern oder vergrößern und verkleinern.

Im aktuellen Artikel werde ich die Handhabung der Dock-Eigenschaft für gebundene Objekte nur im deaktivierten Modus zur automatischen Größenänderung des Bedienfelds implementieren. Um die Objektkante zu definieren, an der das aktuelle Objekt aus der Liste der gebundenen Objekte befestigt ist, erstellen wir vier Objekte nach der Anzahl der Kanten – oben, unten, links und rechts. Jedes Objekt, für das die Dock-Eigenschaft festgelegt ist, passt sich selbst in eines dieser Objekte ein, sodass die restlichen Objekte aus der Liste die Koordinaten des Objekts „kennen“ können, an das sie gebunden werden sollen (da sie an den Kanten angehängt sind des vorherigen Objekts in der Liste, wenn die Dock-Eigenschaft auch dafür festgelegt ist).

Da jetzt alle WinForms-Objekte vom Basis-WinForms-Objekt abgleitet werden, dann anstelle der enthaltenen Formularobjektdatei:

//+------------------------------------------------------------------+
//|                                                        Panel.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\..\Form.mqh"
//+------------------------------------------------------------------+
//| Panel object class of WForms controls                            |
//+------------------------------------------------------------------+
class CPanel : public CForm

werden wir die Datei des Basis-WinForms-Objekts einbinden:

//+------------------------------------------------------------------+
//|                                                        Panel.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\..\WForms\WinFormBase.mqh"
//+------------------------------------------------------------------+
//| Panel object class of WForms controls                            |
//+------------------------------------------------------------------+


Im privaten Abschnitt der Klasse deklarieren wir die Zeiger auf vier Objekte, um die Zeiger auf die Objekte zu speichern, deren Koordinaten wir zum Binden von Dock-Objekten benötigen:

class CPanel : public CWinFormBase
  {
private:
   CGCnvElement     *m_obj_top;                                      // Pointer to the object whose coordinates the current upper object is bound to
   CGCnvElement     *m_obj_bottom;                                   // Pointer to the object whose coordinates the current bottom object is bound to
   CGCnvElement     *m_obj_left;                                     // Pointer to the object whose coordinates the current left object is bound to
   CGCnvElement     *m_obj_right;                                    // Pointer to the object whose coordinates the current right object is bound to
   CGCnvElement     *m_underlay;                                     // Underlay for placing elements
   bool              m_autoscroll;                                   // Auto scrollbar flag
   int               m_autoscroll_margin[2];                         // Array of fields around the control during an auto scroll
   ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode;                 // Mode of the element auto resizing depending on the content
//--- Create a new graphical object
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string name,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);
//--- Return the initial coordinates of a bound object
   virtual void      GetCoords(int &x,int &y);
//--- Create the underlay object
   bool              CreateUnderlayObj(void);
//--- Set the underlay as a coordinate system zero
   void              SetUnderlayAsBase(void);

protected:

Deklarieren wir anstelle der Methode SetDockingToContainer() die Methode SetUnderlayAsBase(), die das Unterlagenobjekt allen vier oben deklarierten Zeiger zuweist. Unmittelbar nach dem Erstellen eines Objekts wird seine Unterlage als Ursprung der Bindungskoordinaten des ersten gebundenen Objekts verwendet. Wenn Dock-Objekte platziert werden, werden die Zeiger auf die entsprechenden Objekte, die bereits an die erforderlichen Kanten gebunden sind, in den Variablen gesetzt. Allerdings dient die Unterlage gleich zu Beginn als Bindeobjekt.

Deklarieren wir im geschützten Abschnitt der Klasse zwei Methoden, die den Maximalwert zurückgeben, mit dem alle Dock-Objekte über das Objekt hinausgehen, an das sie gebunden sind, deklarieren die Methode, die die automatische Größenanpassung der Containergröße an die darin gespeicherten Objekte anpasst, und schreiben vier Methoden, die der Zeiger auf Objekte zurückgeben, deren Kanten von einem Dock-Objekt aus der Liste der an den Container gebundenen Objekte verwendet werden:

protected:
//--- Return the maximum value of Dock object borders going beyond the container by width and (2) height
   int               GetExcessMaxX(void);
   int               GetExcessMaxY(void);
//--- Set (1) X, (2) Y coordinate, (3) width, (4) height and (5) all underlay parameters
   bool              SetCoordXUnderlay(const int value)              { return(this.m_underlay!=NULL ? this.m_underlay.SetCoordX(value) : false);   }
   bool              SetCoordYUnderlay(const int value)              { return(this.m_underlay!=NULL ? this.m_underlay.SetCoordY(value) : false);   }
   bool              SetWidthUnderlay(const int value)               { return(this.m_underlay!=NULL ? this.m_underlay.SetWidth(value)  : false);   }
   bool              SetHeightUnderlay(const int value)              { return(this.m_underlay!=NULL ? this.m_underlay.SetHeight(value) : false);   }
   bool              SetUnderlayParams(void);
//--- Return the underlay (1) X, (2) Y coordinate, (3) width and (4) height
   int               GetCoordXUnderlay(void)                   const { return(this.m_underlay!=NULL ?  this.m_underlay.CoordX() : 0);              }
   int               GetCoordYUnderlay(void)                   const { return(this.m_underlay!=NULL ?  this.m_underlay.CoordY() : 0);              }
   int               GetWidthUnderlay(void)                    const { return(this.m_underlay!=NULL ?  this.m_underlay.Width()  : 0);              }
   int               GetHeightUnderlay(void)                   const { return(this.m_underlay!=NULL ?  this.m_underlay.Height() : 0);              }
//--- Return the underlay (1) X and (2) Y coordinate relative to the panel
   int               GetCoordXUnderlayRelative(void)           const { return(this.m_underlay!=NULL ?  this.m_underlay.CoordXRelative() : 0);      }
   int               GetCoordYUnderlayRelative(void)           const { return(this.m_underlay!=NULL ?  this.m_underlay.CoordYRelative() : 0);      }
   
//--- Return the object whose coordinates the current (1) top, (2) bottom, (3) left or (4) right object is bound to
   CGCnvElement     *GetTopObj(void)                                 { return this.m_obj_top;               }
   CGCnvElement     *GetBottomObj(void)                              { return this.m_obj_bottom;            }
   CGCnvElement     *GetLeftObj(void)                                { return this.m_obj_left;              }
   CGCnvElement     *GetRightObj(void)                               { return this.m_obj_right;             }

//--- Adjust the element size to fit its content
   bool              AutoSizeProcess(const bool redraw);
   
public:

Die restlichen Variablen und Methoden wurden bereits in die Basisklasse des WinForms-Objekts verschoben. Erfahren Sie mehr über solche Änderungen in den Dateien, die dem Artikel beigefügt sind.

Unter Berücksichtigung aller verschobenen Methoden, Anpassungen und neuen Methoden sieht der öffentliche Teil der Klasse nun wie folgt aus:

public:
//--- Return the (1) underlay and (2) the object the current object is bound to
   CGCnvElement     *GetUnderlay(void)                               { return this.m_underlay;              }
//--- Update the coordinates (shift the canvas)
   virtual bool      Move(const int x,const int y,const bool redraw=false);
//--- Set the (1) X, (2) Y coordinates, (3) element width and (4) height
   virtual bool      SetCoordX(const int coord_x);
   virtual bool      SetCoordY(const int coord_y);
   virtual bool      SetWidth(const int width);
   virtual bool      SetHeight(const int height);
//--- Create a new attached element
   virtual bool      CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                      CGCnvElement *main,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool activity,
                                      const bool redraw);
//--- Redraw the object
   virtual void      Redraw(bool redraw);
//--- Update the element
   void              Update(const bool redraw=false)                 { this.m_canvas.Update(redraw);        }
   
//--- Reset the size of all bound objects to the initial ones
   bool              ResetSizeAllToInit(void);
//--- Place bound objects in the order of their Dock binding
   bool              ArrangeObjects(const bool redraw);

//--- (1) Set and (2) return the auto scrollbar flag
   void              SetAutoScroll(const bool flag)                  { this.m_autoscroll=flag;              }
   bool              AutoScroll(void)                                { return this.m_autoscroll;            }
//--- Set the (1) field width, (2) height, (3) the height of all fields around the control during auto scrolling
   void              SetAutoScrollMarginWidth(const int value)       { this.m_autoscroll_margin[0]=value;   }
   void              SetAutoScrollMarginHeight(const int value)      { this.m_autoscroll_margin[1]=value;   }
   void              SetAutoScrollMarginAll(const int value)
                       {
                        this.SetAutoScrollMarginWidth(value); this.SetAutoScrollMarginHeight(value);
                       }
//--- Return the (1) field width and (2) height around the control during auto scrolling
   int               AutoScrollMarginWidth(void)               const { return this.m_autoscroll_margin[0];  }
   int               AutoScrollMarginHeight(void)              const { return this.m_autoscroll_margin[1];  }
  
//--- (1) Set the flag of the element auto resizing depending on the content
   virtual void      SetAutoSize(const bool flag,const bool redraw)
                       {
                        bool prev=this.AutoSize();
                        if(prev==flag)
                           return;
                        CWinFormBase::SetAutoSize(flag,redraw);
                        if(prev!=this.AutoSize() && this.ElementsTotal()>0)
                           this.AutoSizeProcess(redraw);
                       }
//--- (1) Set and (2) return the mode of the element auto resizing depending on the content
   void              SetAutoSizeMode(const ENUM_CANV_ELEMENT_AUTO_SIZE_MODE mode,const bool redraw)
                       {
                        ENUM_CANV_ELEMENT_AUTO_SIZE_MODE prev=this.AutoSizeMode();
                        if(prev==mode)
                           return;
                        this.m_autosize_mode=mode;
                        if(prev!=this.AutoSizeMode() && this.ElementsTotal()>0)
                           this.AutoSizeProcess(redraw);
                       }
   ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void)         const { return this.m_autosize_mode;         }
   
//--- (1) Set and (2) return the mode of binding element borders to the container
   virtual void      SetDockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode,const bool redraw)
                       {
                        if(this.DockMode()==mode)
                           return;
                        CWinFormBase::SetDockMode(mode,redraw);
                        CPanel *base=this.GetBase();
                        if(base!=NULL)
                           base.ArrangeObjects(redraw);
                       }

//--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control
   virtual void      SetPaddingLeft(const uint value)
                       {
                        CWinFormBase::SetPaddingLeft(value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Set the underlay shift along the X axis
                           this.m_underlay.SetCoordXRelative(this.PaddingLeft());
                           //--- Set the X coordinate and the underlay width
                           this.SetCoordXUnderlay(this.CoordX()+this.PaddingLeft());
                           this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight());
                          }
                       }
   virtual void      SetPaddingTop(const uint value)
                       {
                        CWinFormBase::SetPaddingTop(value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Set the underlay shift along the Y axis
                           this.m_underlay.SetCoordYRelative(this.PaddingTop());
                           //--- Set the Y coordinate and underlay height
                           this.SetCoordYUnderlay(this.CoordY()+this.PaddingTop());
                           this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom());
                          }
                       }
   virtual void      SetPaddingRight(const uint value)
                       {
                        CWinFormBase::SetPaddingRight(value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Set the underlay width
                           this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight());
                          }
                       }
   virtual void      SetPaddingBottom(const uint value)
                       {
                        CWinFormBase::SetPaddingBottom(value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Set the underlay height
                           this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom());
                          }
                       }
   virtual void      SetPaddingAll(const uint value)
                       {
                        this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value);
                       }
   
//--- Set the width of the form frame (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides of the control
   virtual void      SetFrameWidthLeft(const uint value)
                       {
                        this.m_frame_width_left=(int)value;
                        if(this.PaddingLeft()<this.FrameWidthLeft())
                           this.SetPaddingLeft(this.FrameWidthLeft());
                       }
   virtual void      SetFrameWidthTop(const uint value)
                       {
                        this.m_frame_width_top=(int)value;
                        if(this.PaddingTop()<this.FrameWidthTop())
                           this.SetPaddingTop(this.FrameWidthTop());
                       }
   virtual void      SetFrameWidthRight(const uint value)
                       {
                        this.m_frame_width_right=(int)value;
                        if(this.PaddingRight()<this.FrameWidthRight())
                           this.SetPaddingRight(this.FrameWidthRight());
                       }
   virtual void      SetFrameWidthBottom(const uint value)
                       {
                        this.m_frame_width_bottom=(int)value;
                        if(this.PaddingBottom()<this.FrameWidthBottom())
                           this.SetPaddingBottom(this.FrameWidthBottom());
                       }
   virtual void      SetFrameWidthAll(const uint value)
                       {
                        this.SetFrameWidthLeft(value); this.SetFrameWidthTop(value); this.SetFrameWidthRight(value); this.SetFrameWidthBottom(value);
                       }

//--- Constructors
                     CPanel(const long chart_id,
                           const int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CPanel(const string name) : CWinFormBase(::ChartID(),0,name,0,0,0,0)
                       {
                        CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_PANEL);
                        CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_PANEL);
                        this.m_type=OBJECT_DE_TYPE_GWF_PANEL; 
                        this.SetForeColor(CLR_DEF_FORE_COLOR);
                        this.SetFontBoldType(FW_TYPE_NORMAL);
                        this.SetMarginAll(3);
                        this.SetPaddingAll(0);
                        this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
                        this.SetBorderStyle(FRAME_STYLE_BEVEL);
                        this.SetAutoScroll(false);
                        this.SetAutoScrollMarginAll(0);
                        this.SetAutoSize(false,false);
                        this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false);
                        this.Initialize();
                        if(this.CreateUnderlayObj())
                           this.SetUnderlayAsBase();
                       }
//--- Destructor
                    ~CPanel();
  };
//+------------------------------------------------------------------+

Alle Set-Methoden haben jetzt das Präfix „set“ in ihren Namen.

Die Methoden zum Setzen der Auto-Resizing, des Auto-Resizing-Modus und die Methoden zum Binden von Dock-Objekten an den Container rufen zunächst die Methode der Basisklasse zum Setzen der Eigenschaft auf. Dann rufen sie die Methode zur Größenänderung des Containers auf:

   virtual void      SetAutoSize(const bool flag,const bool redraw)
                       {
                        bool prev=this.AutoSize();
                        if(prev==flag)
                           return;
                        CWinFormBase::SetAutoSize(flag,redraw);
                        if(prev!=this.AutoSize() && this.ElementsTotal()>0)
                           this.AutoSizeProcess(redraw);
                       }

Die Größe des Containers sollte nur geändert werden, wenn mindestens ein Objekt an den Container gebunden ist.

Andere ähnliche Verfahren werden mit ähnlicher Logik durchgeführt. Sie können sie auf eigene Faust erkunden.
Wenn Sie Fragen haben, können Sie diese gerne im Kommentarteil stellen.

In der Methode, die die Einstellung wir alle Unterlagenparameter festlegt, geben wir den Objekttyp „underlay“ (Unterlage) an:

//+------------------------------------------------------------------+
//| Set all underlay parameters                                      |
//+------------------------------------------------------------------+
bool CPanel::SetUnderlayParams(void)
  {
//--- Set the object type
   this.m_underlay.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_UNDERLAY);
//--- Set the underlay shift values to the variables by X and Y axes
   this.m_underlay.SetCoordXRelative(this.PaddingLeft());
   this.m_underlay.SetCoordYRelative(this.PaddingTop());
//--- Set the underlay coordinates and size
   bool res=true;
   res &=this.SetCoordXUnderlay(this.CoordX()+this.PaddingLeft());
   res &=this.SetCoordYUnderlay(this.CoordY()+this.PaddingTop());
   res &=this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight());
   res &=this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom());
   return res;
  }
//+------------------------------------------------------------------+

Die Kenntnis eines ausgewählten Objekts vom Typ „underlay“ ist erforderlich, wenn Dock-Objekte innerhalb des Containers platziert werden. Wenn ein Objekt, dessen Kanten verwendet werden sollen, um das aktuelle Objekt aus der Liste der gebundenen Objekte anzuhängen, eine Unterlage ist, dann ist die Bindungskante beispielsweise links eine Unterlagen-X-Koordinate. Wenn es sich nicht um eine Unterlage, sondern um ein anderes Dock-Objekt handelt, wird die rechte Kante dieses Dock-Objekts als Bindungskante betrachtet.

Bei den Methoden zum Setzen der Paneelkoordinaten und ihrer Größe rufen wir zunächst die Methode zum Setzen der Basisobjekteigenschaften auf. Dann ändern wir die Koordinaten und die Größe des Unterlageobjekts:

//+------------------------------------------------------------------+
//| Set the panel X coordinate                                       |
//+------------------------------------------------------------------+
bool CPanel::SetCoordX(const int coord_x)
  {
   if(!CGCnvElement::SetCoordX(coord_x))
      return false;
   return(this.m_underlay!=NULL ? this.SetCoordXUnderlay(coord_x+this.PaddingLeft()) : true);
  }
//+------------------------------------------------------------------+
//| Set the panel Y coordinate                                       |
//+------------------------------------------------------------------+
bool CPanel::SetCoordY(const int coord_y)
  {
   if(!CGCnvElement::SetCoordY(coord_y))
      return false;
   return(this.m_underlay!=NULL ? this.SetCoordYUnderlay(coord_y+this.PaddingTop()) : true);
  }
//+------------------------------------------------------------------+
//| Set the panel width                                              |
//+------------------------------------------------------------------+
bool CPanel::SetWidth(const int width)
  {
   if(!CGCnvElement::SetWidth(width))
      return false;
   return(this.m_underlay!=NULL ? this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()) : true);
  }
//+------------------------------------------------------------------+
//| Set the panel height                                             |
//+------------------------------------------------------------------+
bool CPanel::SetHeight(const int height)
  {
   if(!CGCnvElement::SetHeight(height))
      return false;
   return(this.m_underlay!=NULL ? this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()) : true);
  }
//+------------------------------------------------------------------+


Die Methode, die die Größe aller gebundenen Objekte auf die ursprünglichen zurücksetzt:

//+------------------------------------------------------------------+
//| Reset the size of all bound objects to the initial ones          |
//+------------------------------------------------------------------+
bool CPanel::ResetSizeAllToInit(void)
  {
   bool res=true;
   for(int i=0;i<this.ElementsTotal();i++)
     {
      CPanel *obj=this.GetElement(i);
      if(obj==NULL)
        {
         res &=false;
         continue;
        }
      res &=obj.Resize(i,obj.GetWidthInit(),obj.GetHeightInit());
     }
   return res;
  }
//+------------------------------------------------------------------+

In der Schleife aller an den Container gebundenen Objekte rufen wir das nächste Objekt ab und setzen das Ergebnis der Größenänderung des Objekts auf seine ursprüngliche Größe in der Variablen res. Nach dem Schleifenende wird das Gesamtergebnis der Größenänderung in der Variablen res gesetzt. Wenn die Größenänderung mindestens eines Objekts mit einem Fehler endet, wird die Variable res auf false gesetzt, andernfalls auf true.


Die Methode, die ein neues angehängtes Element erstellt:

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CPanel::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                              CGCnvElement *main,
                              const int x,
                              const int y,
                              const int w,
                              const int h,
                              const color colour,
                              const uchar opacity,
                              const bool activity,
                              const bool redraw)
  {
//--- If failed to create a new graphical element, return 'false'
   CGCnvElement *obj=CForm::CreateAndAddNewElement(element_type,main,x,y,w,h,colour,opacity,activity);
   if(obj==NULL)
      return false;
//--- If the object type is a base WinForms object and higher,
   if(obj.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE)
     {
      //--- declare the pointer with the base WinForms object type and assign the pointer to the newly created object to it,
      //--- set the frame color equal to the background color 
      CWinFormBase *wf=obj;
      wf.SetColorFrame(wf.ColorBackground());
     }
//--- If the panel has auto resize enabled and features bound objects, call the resize method
   if(this.AutoSize() && this.ElementsTotal()>0)
      this.AutoSizeProcess(redraw);
//--- Redraw the panel and all added objects, and return 'true'
   this.Redraw(redraw);
   return true;
  }
//+------------------------------------------------------------------+

Die Methodenlogik wird in den Codekommentaren beschrieben und ist recht einfach.

Die Methode, die den maximalen Wert von Dock-Objektgrenzen zurückgibt, die in der Breite über den Container hinausgehen:

//+------------------------------------------------------------------+
//| Return the maximum value of Dock object borders                  |
//| going beyond the container by width                              |
//+------------------------------------------------------------------+
int CPanel::GetExcessMaxX(void)
  {
   int value=0;
   for(int i=0;i<this.ElementsTotal();i++)
     {
      CWinFormBase *element=this.GetElement(i);
      if(element==NULL)
         continue;
      if(element.RightEdge()>value)
         value=element.RightEdge();
     }
   return value-this.m_underlay.RightEdge();
  }
//+------------------------------------------------------------------+

Die Methode, die den maximalen Wert von Dock-Objektgrenzen zurückgibt, die in der Höhe über den Container hinausgehen:

//+------------------------------------------------------------------+
//| Return the maximum value of Dock object borders                  |
//| going beyond the container by height                             |
//+------------------------------------------------------------------+
int CPanel::GetExcessMaxY(void)
  {
   int value=0;
   for(int i=0;i<this.ElementsTotal();i++)
     {
      CWinFormBase *element=this.GetElement(i);
      if(element==NULL)
         continue;
      if(element.BottomEdge()>value)
         value=element.BottomEdge();
     }
   return value-this.m_underlay.BottomEdge();
  }
//+------------------------------------------------------------------+

Die Logik hinter beiden Methoden ist identisch: Wir holen uns in der Schleife aller an den Container gebundenen Objekte das nächste Objekt, und wenn seine rechte (untere) Kante den in der Wertvariablen festgelegten Wert überschreitet, wird der Kantenwert dieser Variablen zugewiesen. Nach Schleifenende weist der Variablenwert den maximalen Wert der rechten (unteren) Kante aller Objekte auf, und die Grenze zwischen dem gefundenen Wert und der rechten (unteren) Unterlageobjektkante wird zurückgegeben. Somit erhalten wir einen positiven oder negativen Wert in Pixel, um den wir die Größe des Containers ändern müssen.


Die Methode, die gebundene Objekte in der Reihenfolge ihrer Dock-Bindung platziert:

//+------------------------------------------------------------------+
//| Place bound objects in the order of their Dock binding           |
//+------------------------------------------------------------------+
bool CPanel::ArrangeObjects(const bool redraw)
  {
   CWinFormBase *prev=NULL, *obj=NULL;
   //--- If auto resizing mode is enabled
   if(this.AutoSize())
     {
      //--- In the loop by all bound objects,
      for(int i=0;i<this.ElementsTotal();i++)
        {
         //--- Get the previous element from the list
         prev=this.GetElement(i-1);
         //--- If there is no previous element, set the underlay as a previous element
         if(prev==NULL)
            this.SetUnderlayAsBase();
         //--- Get the current element
         obj=GetElement(i);
         //--- If the object has not been received or its type is less than the base WinForms object or it has no underlay, move on
         if(obj==NULL || obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || this.m_underlay==NULL)
            continue;
         //--- Depending on the current object binding mode...
         //--- Top
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP)
           {
            
           }
         //--- Bottom
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM)
           {
            
           }
         //--- Left
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT)
           {
            
           }
         //--- Right
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT)
           {
            
           }
         //--- Binding with filling
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL)
           {
            
           }
         //--- No binding
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_NONE)
           {
            
           }
        }
      this.Resize(this.GetWidthInit(),this.GetHeightInit(),false);
     }
   //--- If auto resizing mode disabled 
   else
     {
      //--- In the loop by all bound objects,
      for(int i=0;i<this.ElementsTotal();i++)
        {
         //--- Get the current and previous elements from the list
         obj=this.GetElement(i);
         prev=this.GetElement(i-1);
         //--- If there is no previous element, set the underlay as a previous element
         if(prev==NULL)
            this.SetUnderlayAsBase();
         //--- If the object has not been received or its type is less than the base WinForms object or it has no underlay, move on
         if(obj==NULL || obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || this.m_underlay==NULL)
            continue;
         int x=0, y=0; // Object binding coordinates
         //--- Depending on the current object binding mode...
         //--- Top
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP)
           {
            //--- If failed to change the object size (for the entire underlay width and by the initial object height), move on to the next one
            if(!obj.Resize(this.GetWidthUnderlay(),obj.GetHeightInit(),false))
               continue;
            //--- Get the pointer to the object at the top whose edges are used to bind the current one
            CGCnvElement *coord_base=this.GetTopObj();
            //--- Get the object binding coordinates
            x=this.GetCoordXUnderlay();
            y=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.CoordY() : coord_base.BottomEdge()+1);
            //--- If failed to move the object to the obtained coordinates, move on to the next one
            if(!obj.Move(x,y,false))
               continue;
            //--- Set the current object as the top one whose edges will be used to bind the next one
            this.m_obj_top=obj;
           }
         //--- Bottom
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM)
           {
            //--- If failed to change the object size (for the entire underlay width and by the initial object height), move on to the next one
            if(!obj.Resize(this.GetWidthUnderlay(),obj.GetHeightInit(),false))
               continue;
            //--- Get the pointer to the object at the bottom whose edges are used to bind the current one
            CGCnvElement *coord_base=this.GetBottomObj();
            //--- Get the object binding coordinates
            x=this.GetCoordXUnderlay();
            y=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.BottomEdge()-obj.Height()-1 : coord_base.CoordY()-obj.Height()-1);
            //--- If failed to move the object to the obtained coordinates, move on to the next one
            if(!obj.Move(x,y,false))
               continue;
            //--- Set the current object as the bottom one whose edges will be used to bind the next one
            this.m_obj_bottom=obj;
           }
         //--- Left
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT)
           {
            //--- If failed to change the object size (for the initial object width and the entire underlay height), move on to the next one
            if(!obj.Resize(obj.GetWidthInit(),this.GetHeightUnderlay(),false))
               continue;
            //--- Get the pointer to the object at the left whose edges are used to bind the current one
            CGCnvElement *coord_base=this.GetLeftObj();
            //--- Get the object binding coordinates
            x=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.CoordX() : coord_base.RightEdge()+1);
            y=this.GetCoordYUnderlay();
            //--- If failed to move the object to the obtained coordinates, move on to the next one
            if(!obj.Move(x,y,false))
               continue;
            //--- Set the current object as the left one whose edges will be used to bind the next one
            this.m_obj_left=obj;
           }
         //--- Right
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT)
           {
            //--- If failed to change the object size (for the initial object width and the entire underlay height), move on to the next one
            if(!obj.Resize(obj.GetWidthInit(),this.GetHeightUnderlay(),false))
               continue;
            //--- Get the pointer to the object at the right whose edges are used to bind the current one
            CGCnvElement *coord_base=this.GetRightObj();
            //--- Get the object binding coordinates
            x=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? m_underlay.RightEdge()-obj.Width() : coord_base.CoordX()-obj.Width()-1);
            y=this.GetCoordYUnderlay();
            //--- If failed to move the object to the obtained coordinates, move on to the next one
            if(!obj.Move(x,y,false))
               continue;
            //--- Set the current object as the right one whose edges will be used to bind the next one
            this.m_obj_right=obj;
           }
         //--- Binding with filling
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL)
           {
            //--- If failed to change the object size (for the entire underlay width and height), move on to the next one
            if(!obj.Resize(this.GetWidthUnderlay(),this.GetHeightUnderlay(),false))
               continue;
            //--- Set the underlay as a binding object
            this.SetUnderlayAsBase();
            //--- Get the object binding coordinates
            x=this.GetLeftObj().CoordX();
            y=this.GetTopObj().CoordY();
            //--- If failed to move the object to the obtained coordinates, move on to the next one
            if(!obj.Move(x,y,false))
               continue;
           }
         //--- No binding
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_NONE)
           {
            //--- Reset the object size
            obj.Resize(obj.GetWidthInit(),obj.GetHeightInit(),false);
            //--- Get the initial object location coordinates
            x=this.GetCoordXUnderlay()+obj.CoordXRelativeInit();
            y=this.GetCoordYUnderlay()+obj.CoordYRelativeInit();
            //--- If failed to move the object to the obtained coordinates, move on to the next one
            if(!obj.Move(x,y,false))
               continue;
           }
         //--- Calculate and set the relative object coordinates
         obj.SetCoordXRelative(x-this.m_underlay.CoordX());
         obj.SetCoordYRelative(y-this.m_underlay.CoordY());
        }
     }
//--- Redraw the object with the redraw flag and return 'true'
   this.Redraw(redraw); 
   return true;
  }
//+------------------------------------------------------------------+

Die Methodenlogik wurde in den Codekommentaren ausführlich beschrieben. Falls das Paneel das Auto-Resize-Flag hat, werde ich die Objekte noch nicht im Container anordnen (es gibt nur Platzhalter, die durch Routinen ersetzt werden müssen), da es eine andere Logik haben wird als die, die für das Paneel ohne Auto-Resize implementiert ist .

Die Methode, die die Unterlage als Nullpunkt des Koordinatensystems für gebundene Dock-Objekte festlegt:

//+------------------------------------------------------------------+
//| Set the underlay as a coordinate system zero                     |
//+------------------------------------------------------------------+
void CPanel::SetUnderlayAsBase(void)
  {
   this.m_obj_left=this.m_underlay;
   this.m_obj_right=this.m_underlay;
   this.m_obj_top=this.m_underlay;
   this.m_obj_bottom=this.m_underlay;
  }
//+------------------------------------------------------------------+

Weisen wir den Zeiger für alle vier Bindungsobjekte der Unterlage zu.

Die Methode zum Anpassen der Elementgröße an den Inhalt:

//+------------------------------------------------------------------+
//| Adjust the element size to fit its content                       |
//+------------------------------------------------------------------+
bool CPanel::AutoSizeProcess(const bool redraw)
  {
//--- Get values along X and Y axes, by which the panel size is to be changed
   int excess_w=this.GetExcessMaxX();
   int excess_h=this.GetExcessMaxY();
//--- If failed to change the size, return 'true'
   if(excess_w==0 && excess_h==0)
      return true;
   //--- If failed to change the panel size, return the result of its adjustment
   return
     (
      //--- if only a size increase
      this.AutoSizeMode()==CANV_ELEMENT_AUTO_SIZE_MODE_GROW ? 
      this.Resize(this.Width()+(excess_w>0  ? excess_w : 0),this.Height()+(excess_h>0  ? excess_h : 0),redraw) :
      //--- if both increase and decrease
      this.Resize(this.Width()+(excess_w!=0 ? excess_w : 0),this.Height()+(excess_h!=0 ? excess_h : 0),redraw)
     );
  }
//+------------------------------------------------------------------+

Die Methodenlogik wird in den Codekommentaren beschrieben. Kurz gesagt, wir erhalten die Maximalwerte entlang der X- und Y-Achse, um die die gebundenen Objekte über die Grenzen der Unterlage hinausgehen. Ein positiver Wert zeigt an, dass die Objekte über die Unterlage hinausragen, während ein negativer Wert anzeigt, dass die Unterlage zu groß ist und verkleinert werden kann. Geben wir auch das Ergebnis der Änderung der Paneelgröße unter Berücksichtigung des automatischen Größenänderungsmodus zurück (entweder nur vergrößern oder vergrößern und verkleinern).


In der Datei \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh der Kollektionsklasse der grafischen Elemente müssen wir die Namen der Methoden ersetzen, die die Eigenschaften BorderStyle() und FrameWidthAll() in der Paneel-Erstellungsmethode festlegen, durch die neuen: SetBorderStyle() und SetFrameWidthAll(). In den angehängten Dateien wurden die Methoden bereits umbenannt.

Ersetzen wir in \MQL5\Include\DoEasy\Engine.mqh, nämlich in den Methoden, die das WForm Paneel-Objekt zurückgeben, den Substitutionsnamen des Makros GRAPH_ELEMENT_TYPE_PANEL durch GRAPH_ELEMENT_TYPE_WF_PANEL:

//--- Return the WForm Element object by object ID
   CForm               *GetWFForm(const int element_id)
                          {
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_FORM);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
   
//--- Return the WForm Panel object by object name on the current chart
   CPanel              *GetWFPanel(const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the WForm Panel object by chart ID and object name
   CPanel              *GetWFPanel(const long chart_id,const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the WForm Panel object by object ID
   CPanel              *GetWFPanel(const int element_id)
                          {
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }

//--- Create the WinForm Element object

Das sind alle Änderungen und Verbesserungen, die ich für den aktuellen Artikel geplant habe.


Test

Um den Test durchzuführen, verwenden wir den EA aus dem vorherigen Artikel und speichern ihn unter \MQL5\Experts\TestDoEasy\Part105\ als TestDoEasyPart105.mq5.

Lassen Sie uns das Flag für die automatische Größenänderung des Bedienfelds zum Anpassen an den Inhalt des Bedienfelds sowie den Modus für die automatische Größenänderung hinzufügen.

Fügen wir außerdem einen neuen Schlüssel zum Platzieren von Objekten im Container hinzu. Wenn Sie Q drücken, werden alle Objekte, die sich innerhalb des Paneels befinden, gemäß ihrem Bindungsmodus platziert. Wir haben 6 solcher Objekte entsprechend der Anzahl der Bindungsmodi:

//+------------------------------------------------------------------+
//| Control borders bound to the container                           |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_DOCK_MODE
  {
   CANV_ELEMENT_DOCK_MODE_NONE,                       // Attached to the specified coordinates, size does not change
   CANV_ELEMENT_DOCK_MODE_TOP,                        // Attaching to the top and stretching along the container width
   CANV_ELEMENT_DOCK_MODE_BOTTOM,                     // Attaching to the bottom and stretching along the container width
   CANV_ELEMENT_DOCK_MODE_LEFT,                       // Attaching to the left and stretching along the container height
   CANV_ELEMENT_DOCK_MODE_RIGHT,                      // Attaching to the right and stretching along the container height
   CANV_ELEMENT_DOCK_MODE_FILL,                       // Stretching along the entire container width and height
  };
//+------------------------------------------------------------------+

Dementsprechend erhält jedes dieser Objekte einen Bindungsmodus, der seiner Nummer in der Liste der Paneel-Objekte entspricht. Das erste Objekt in der Liste hat keine Bindung (CANV_ELEMENT_DOCK_MODE_NONE), das zweite den Modus CANV_ELEMENT_DOCK_MODE_TOP, das dritte — MODE_BOTTOM usw.

Fügen wir im globalen Bereich die Makrosubstitution für die Taste Q und Enumeration für Eingaben in Englisch und der Landessprache des Nutzerssowie neue Eingaben hinzu:

//+------------------------------------------------------------------+
//|                                            TestDoEasyPart105.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- defines
#define  FORMS_TOTAL (3)   // 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

//--- enumerations by compilation language
#ifdef COMPILE_EN
enum ENUM_AUTO_SIZE_MODE
  {
   AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW,                // Grow
   AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK   // Grow and Shrink
  };
#else 
enum ENUM_AUTO_SIZE_MODE
  {
   AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW,                // Increase only
   AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK   // Increase and decrease
  };
#endif 
//--- input parameters
sinput   bool                 InpMovable        =  true;                // Movable forms flag
sinput   ENUM_INPUT_YES_NO    InpAutoSize       =  INPUT_YES;           // Autosize
sinput   ENUM_AUTO_SIZE_MODE  InpAutoSizeMode   =  AUTO_SIZE_MODE_GROW; // Autosize Mode
//--- global variables
CEngine        engine;
color          array_clr[];
//+------------------------------------------------------------------+

Entfernen wir den Code zum Erstellen von Formularen und Elementen aus OnInit(). Belassen wir nur die Erstellung eines Paneels. Erstellen wir in der Schleife im Paneel selbst sechs gebundene Paneel-Objekte mit dem Neuzeichen-Flag false. In diesem Fall ist die Höhe des Paneels etwas geringer als die Gesamthöhe aller darin eingebauten Objekte, und die Breite ist im Gegenteil größer. So können wir sehen, wie sich das Paneel an die Größe der darin eingebauten Objekte anpasst:

//+------------------------------------------------------------------+
//| 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 WinForms Panel object
   CPanel *pnl=NULL;
   pnl=engine.CreateWFPanel("WFPanel",50,50,230,150,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true);
   if(pnl!=NULL)
     {
      //--- Set Padding to 4
      pnl.SetPaddingAll(4);
      //--- 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);
      //--- In the loop, create 6 bound panel objects
      for(int i=0;i<6;i++)
        {
         //--- create the panel object with coordinates along the X axis in the center and 10 along the Y axis, the width of 80 and the height of 50
         CPanel *prev=pnl.GetElement(i-1);
         int xb=0, yb=0;
         int x=(i<3 ? (prev==NULL ? xb : prev.CoordXRelative()) : xb+prev.Width()+20);
         int y=(i<3 ? (prev==NULL ? yb : prev.BottomEdgeRelative()+16) : (i==3 ? yb : prev.BottomEdgeRelative()+16));
         pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,pnl,x,y,80,40,C'0xCD,0xDA,0xD7',200,true,false);
        }
      pnl.Redraw(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Nachdem wir alle Elemente innerhalb des Paneels erstellt haben, zeichnen wir das gesamte Paneel zusammen mit den darin erstellten Elementen vollständig neu (Neuzeichen-Flag true)

Fügen wir in der Ereignisbehandlung von OnChartEvent(), nämlich im Block der Tastenanschläge, die Routine für Q hinzu:

   //--- If a key is pressed
   if(id==CHARTEVENT_KEYDOWN)
     {
      CPanel *panel=engine.GetWFPanel(0);
      if(panel!=NULL && (lparam==KEY_UP || lparam==KEY_DOWN || lparam==KEY_LEFT || lparam==KEY_RIGHT || lparam==KEY_FILL || lparam==KEY_ORIGIN || lparam==KEY_INDEX))
        {
         for(int i=0;i<panel.ElementsTotal();i++)
           {
            CPanel *obj=panel.GetElement(i);
            if(obj!=NULL)
              {
               if(lparam==KEY_UP)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_TOP,false);
               else if(lparam==KEY_DOWN)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_BOTTOM,false);
               else if(lparam==KEY_LEFT)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_LEFT,false);
               else if(lparam==KEY_RIGHT)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_RIGHT,false);
               else if(lparam==KEY_FILL)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_FILL,false);
               else if(lparam==KEY_ORIGIN)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
               else if(lparam==KEY_INDEX)
                 {
                  obj.SetDockMode((ENUM_CANV_ELEMENT_DOCK_MODE)i,true);
                  Sleep(i>0 ? 500 : 0);
                 }
              }
           }
         panel.Redraw(true);
        }
     }

Um deutlich zu sehen, wie sich Objekte gemäß dem Bindungsmodus zu ihren Plätzen bewegen, nehmen wir nach jeder nächsten Bewegung des Objekts zu neuen Koordinaten eine Verzögerung von einer halben Sekunde vor.

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


Wie wir sehen können, sind die Objekte korrekt an jede der Paneelseiten gebunden. Wenn Sie Q drücken, wird jedes Objekt an der entsprechenden Paneelseite befestigt. Beim Ändern der Modi für die automatische Größenänderung des Bedienfelds passt es sich gemäß dem Modus für die automatische Größenanpassung an seinen internen Inhalt an.

Was kommt als Nächstes?

Im nächsten Artikel werde ich meine Arbeit an WinForms-Objekten 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. Hinterlassen Sie Ihre Fragen, Kommentare und Vorschläge im Kommentarteil.

Zurück zum Inhalt

*Vorherige Artikel in dieser Reihe:

DoEasy. Steuerung (Teil 1): Erste Schritte
DoEasy. Steuerung (Teil 2): Arbeiten an der Klasse CPanel
DoEasy. Steuerung (Teil 3): Erstellen gebundener Steuerelemente
DoEasy. Steuerung (Teil 4): Paneel-Steuerung, Parameter für Padding und Dock


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

Beigefügte Dateien |
MQL5.zip (4347.09 KB)
Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 12): Times and Trade (I) Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 12): Times and Trade (I)
Heute werden wir „Times and Trade“ (Zeiten und Handel) mit einer schnellen Interpretation erstellen, um den Auftragsfluss zu lesen. Es ist der erste Teil, in dem wir das System aufbauen werden. Im nächsten Artikel vervollständigen wir das System mit den fehlenden Informationen. Um diese neue Funktionsweisen zu implementieren, müssen wir dem Code unseres Expert Advisors mehrere neue Dinge hinzufügen.
Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 11): System von Kreuzaufträgen Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 11): System von Kreuzaufträgen
In diesem Artikel werden wir ein System von Kreuzaufträgen (cross order system) erstellen. Es gibt eine Art von Vermögenswerten, die den Händlern das Leben sehr schwer macht - Terminkontrakte. Aber warum machen sie einem das Leben schwer?
Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 13): Times and Trade (II) Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 13): Times and Trade (II)
Heute werden wir den zweiten Teil des Systems Times & Trade (Zeiten und Handel) zur Marktanalyse aufbauen. Im vorangegangenen Artikel „Times & Trade (I)“ haben wir eine alternative Chartorganisation besprochen, die es erlauben würde, einen Indikator für die schnellstmögliche Interpretation der am Markt getätigten Geschäfte zu haben.
Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 10): Zugriff auf nutzerdefinierte Indikatoren Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 10): Zugriff auf nutzerdefinierte Indikatoren
Wie kann man auf nutzerdefinierte Indikatoren direkt in einem Expert Advisor zugreifen? Ein Handels-EA kann nur dann wirklich nützlich sein, wenn er nutzerdefinierte Indikatoren verwenden kann; andernfalls ist er nur ein Satz von Codes und Anweisungen.