DoEasy. Steuerung (Teil 22): SplitContainer. Ändern der Eigenschaften des erstellten Objekts

Artyom Trishkin | 8 Dezember, 2022

Inhalt


Konzept

Das Steuerelement SplitContainer wird in der Bibliothek mit Standardwerten erstellt. Wir können die Eigenschaften eines Objekts ändern, nachdem wir es erstellt haben, aber sein Aussehen wird sich nicht ändern. Um dies zu verhindern, zeichnen wir das Objekt mit den neuen Werten seiner Eigenschaften neu, nachdem wir seine Eigenschaft geändert haben.

In diesem relativ kleinen Artikel werden wir die Methoden zum Einstellen der Eigenschaften eines Steuerelements fertigstellen, sodass alle Änderungen an seinen Eigenschaften sofort sein Aussehen verändern können.


Verbesserung der Bibliotheksklassen

In \MQL5\Include\DoEasy\Defines.mqh fügen wir zwei neue Ereignisse zur Liste der möglichen Ereignisse für WinForms-Steuerelemente hinzu:

//+------------------------------------------------------------------+
//| List of possible WinForms control events                         |
//+------------------------------------------------------------------+
enum ENUM_WF_CONTROL_EVENT
  {
   WF_CONTROL_EVENT_NO_EVENT = GRAPH_OBJ_EVENTS_NEXT_CODE,// No event
   WF_CONTROL_EVENT_CLICK,                            // "Click on the control" event
   WF_CONTROL_EVENT_CLICK_CANCEL,                     // "Canceling the click on the control" event
   WF_CONTROL_EVENT_MOVING,                           // "Control relocation" event
   WF_CONTROL_EVENT_STOP_MOVING,                      // "Control relocation stop" event
   WF_CONTROL_EVENT_TAB_SELECT,                       // "TabControl tab selection" event
   WF_CONTROL_EVENT_CLICK_SCROLL_LEFT,                // "Clicking the control left button" event
   WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT,               // "Clicking the control right button" event
   WF_CONTROL_EVENT_CLICK_SCROLL_UP,                  // "Clicking the control up button" event
   WF_CONTROL_EVENT_CLICK_SCROLL_DOWN,                // "Clicking the control down button" event
                                     
  };
#define WF_CONTROL_EVENTS_NEXT_CODE (WF_CONTROL_EVENT_CLICK_SCROLL_DOWN+1)  // The code of the next event after the last graphical element event code
//+------------------------------------------------------------------+

Einige Steuerelemente sollten auf ihre Bewegung reagieren und Ereignisse an das Programm oder an das übergeordnete Steuerelement senden, in dem sie arbeiten. Wenn zum Beispiel ein Schieberegler im Bildlaufbereich von der Maus erfasst und bewegt wird, sollte er ein Ereignis über seine Bewegung und die Werte, um die er bewegt wurde, senden. In ähnlicher Weise sollte der Separator im SplitContainer-Steuerelement auf seine Bewegung reagieren.

In der derzeitigen Implementierung ändert es sein Aussehen, wenn der Mauszeiger über ihm schwebt. Wenn es verschoben wird, sendet es ein Ereignis an das übergeordnete Element, das auf dieses Ereignis reagiert, indem es die Größe seiner Felder ändert. Anschließend werde ich dieses Verhalten abschließen, und das Objekt wird beim Bewegen der Maus durch ein mit einer gepunkteten Linie gezeichnetes Rechteck umrandet. Wird er mit der Maus erfasst, wird er mit einer schraffierten Fläche gefüllt. Wird die Maus losgelassen (und damit die Verschiebung abgeschlossenen), erhält die Trennlinie wieder ihr ursprüngliches Aussehen.

Ich habe zwei neue Ereignisse hinzugefügt, um auf solche Ereignisse (Bewegen eines Steuerelements und Beenden seiner Bewegung) korrekt und rechtzeitig zu reagieren. Aus der Liste habe ich das allerletzte Ereignis WF_CONTROL_EVENT_SPLITTER_MOVE entfernt, da es nun durch das Ereignis WF_CONTROL_EVENT_MOVING ersetzt werden kann, das universell für alle Steuerelemente ist. Da das letzte Ereignis in der Liste nun das Ereignis WF_CONTROL_EVENT_CLICK_SCROLL_DOWN ist, geben wir es in die Berechnung des nächsten Ereigniscode-Werts ein.


Wenn wir den EA aus dem vorangegangenen Artikel ausführen und das Hauptpanel verschieben, wird seine Trennlinie auf dem SplitContainer-Steuerelement nicht sichtbar sein. Dies ist korrekt, da sie ausgeblendet werden sollte, bis der Mauszeiger darüber schwebt. Wenn die Trennlinie jedoch mindestens einmal verschoben wird, wird das ausgeblendete Steuerelement bei jeder Bewegung des Hauptfensters eingeblendet.

Dies ist ein Logikfehler, der schwer zu finden war. Aber jetzt habe ich die Lösung. Die Methode BringToTop() funktioniert so, dass sie das Objekt erst ausblendet und dann einblendet, wodurch es in den Vordergrund gebracht wird. In dieser Methode wurde die ausgeblendete Trennlinie sichtbar, da die Methode den Zustand der Flag, die anzeigt, dass das Element gezeichnet werden muss, nicht berücksichtigt.

In \MQL5\Include\DoEasy\Objects\Graph\Form.mqh, und zwar in der Methode, die das Objekt über alle anderen setzt, fügen wir die Überprüfung der Flag hinzu, die die Notwendigkeit des Zeichnens des Elements anzeigt. Wenn das Objekt nicht angezeigt werden soll, dann lassen wir die Methode einfach stehen:

//+------------------------------------------------------------------+
//| Set the object above all the rest                                |
//+------------------------------------------------------------------+
void CForm::BringToTop(void)
  {
//--- If the object should not be displayed, leave
   if(!this.Displayed())
      return;
//--- If the shadow usage flag is set
   if(this.m_shadow)
     {
      //--- If the shadow object is created, move it to the foreground
      if(this.m_shadow_obj!=NULL)
         this.m_shadow_obj.BringToTop();
     }
//--- Move the object to the foreground (the object is located above the shadow)
   CGCnvElement::BringToTop();
//--- In the loop by all bound objects,
   int total=this.m_list_elements.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the next object from the list
      CGCnvElement *obj=this.m_list_elements.At(i);
      if(obj==NULL)
         continue;
      //--- and bring it to the foreground if the object should be displayed
      if(!obj.Displayed())
         continue;
      obj.BringToTop();
     }
  }
//+------------------------------------------------------------------+

Wir fügen die gleiche Prüfung der Liste aller angehängten Objekte hinzu — wir überspringen die Objekte, für die das Flag für die Notwendigkeit des Zeichnens deaktiviert ist, um zu verhindern, dass das Objekt in den Vordergrund gebracht wird.

In der Ereignisbehandlung des letzten Mausereignisses füge wir die Überprüfung des Objekts Zeichnungsnotwendigkeit Flag hinzu:

//+------------------------------------------------------------------+
//| Last mouse event handler                                         |
//+------------------------------------------------------------------+
void CForm::OnMouseEventPostProcessing(void)
  {
   if(!this.IsVisible() || !this.Enabled() || !this.Displayed())
      return;
   ENUM_MOUSE_FORM_STATE state=this.GetMouseState();
   switch(state)
     {
      //--- The cursor is outside the form, the mouse buttons are not clicked
      //---...
      //---...

Zuvor wurde das letzte Mausereignis bei ausgeblendeten und inaktiven Objekten nicht behandelt. Derzeit werden die Objekte, die nicht angezeigt werden sollen, nicht so gut behandelt.


In der Objektklasse des SplitContainer-Bedienfelds in \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\SplitContainerPanel.mqh fügen wir die Überprüfung des Objektzeichnungsnotwendigkeitsflags in den Methoden zum Löschen von Elementen hinzu:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CSplitContainerPanel::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
//--- If the element should not be displayed (hidden inside another control), leave
   if(!this.Displayed())
      return;
//--- Fill the element having the specified color and the redrawing flag
   CGCnvElement::EraseNoCrop(colour,opacity,false);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE)
      this.DrawFrame();
//--- Update the element having the specified redrawing flag
   this.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CSplitContainerPanel::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false)
  {
//--- If the element should not be displayed (hidden inside another control), leave
   if(!this.Displayed())
      return;
//--- Fill the element having the specified color array and the redrawing flag
   CGCnvElement::EraseNoCrop(colors,opacity,vgradient,cycle,false);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE)
      this.DrawFrame();
//--- Update the element having the specified redrawing flag
   this.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Diese Prüfungen dienen auch dazu, zu verhindern, dass das Panel unbeabsichtigt angezeigt wird, wenn es im SplitContainer-Steuerelement ausgeblendet (zusammengeklappt) ist.

Wenn die Trennlinie des SplitContainer-Steuerelements das Flag für eine feste Trennlinie gesetzt hat, sollte es in keiner Weise mit der Maus interagieren. Wenn der Mauszeiger über dem Feld schwebt (der Mauszeiger hat das Trennzeichen verlassen und ist in das Feld eingetreten), ist es nicht erforderlich, ein solches Ereignis für die feste Trennlinie zu behandeln.

Wir fügen die Überprüfung der festen Trennlinie im Ereignis-Handler „Der Cursor befindet sich innerhalb des aktiven Bereichs, die Maustasten sind nicht angeklickt“ hinzu:

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

Ein solches Steuerelement schränkt uns in der Tat nur von unnötigen Handlungen ein. Es besteht keine Notwendigkeit, eine Trennlinie hoch zu holen und sie auszublenden, wenn sie bereits von Anfang an ausgeblendet ist.

Alle wichtigen Änderungen und Verbesserungen werden in der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\SplitContainer.mqh der SplitContainer WinForms-Objekte vorgenommen.

Wir deklarieren die neuen Methoden zum Zusammenklappen/Erweitern der Bedienfelder im privaten Bereich der Klasse:

//--- Set the panel parameters
   bool              SetsPanelParams(void);

//--- (1) Collapse and (2) expand the panel 1
   void              CollapsePanel1(void);
   void              ExpandPanel1(void);
//--- (1) Collapse and (2) expand the panel 2
   void              CollapsePanel2(void);
   void              ExpandPanel2(void);

public:


Wir implementieren die deklarierten Methoden außerhalb des Klassenkörpers.

Das eingeklappte Panel wird ausgeblendet, und die Eigenschaft für die Darstellung wird auf false gesetzt:

//+------------------------------------------------------------------+
//| Collapse the panel 1                                             |
//+------------------------------------------------------------------+
void CSplitContainer::CollapsePanel1(void)
  {
   CSplitContainerPanel *panel=this.GetPanel1();
   if(panel==NULL)
      return;
   panel.SetDisplayed(false);
   panel.Hide();
  }
//+------------------------------------------------------------------+
//| Collapse the panel 2                                             |
//+------------------------------------------------------------------+
void CSplitContainer::CollapsePanel2(void)
  {
   CSplitContainerPanel *panel=this.GetPanel2();
   if(panel==NULL)
      return;
   panel.SetDisplayed(false);
   panel.Hide();
  }
//+------------------------------------------------------------------+


Für das erweiterte Paneel stellen wir die Notwendigkeit der Darstellung ein, zeigen das Paneel an und bringen es in den Vordergrund:

//+------------------------------------------------------------------+
//| Expand the panel 1                                               |
//+------------------------------------------------------------------+
void CSplitContainer::ExpandPanel1(void)
  {
   CSplitContainerPanel *panel=this.GetPanel1();
   if(panel==NULL)
      return;
   panel.SetDisplayed(true);
   panel.Show();
   panel.BringToTop();
  }
//+------------------------------------------------------------------+
//| Expand the panel 2                                               |
//+------------------------------------------------------------------+
void CSplitContainer::ExpandPanel2(void)
  {
   CSplitContainerPanel *panel=this.GetPanel2();
   if(panel==NULL)
      return;
   panel.SetDisplayed(true);
   panel.Show();
   panel.BringToTop();
  }
//+------------------------------------------------------------------+


Einige der Methoden zum Festlegen von Eigenschaften für Objekte in der Bibliothek können entweder einfach einen Wert für eine Eigenschaft festlegen oder einen Wert für eine Eigenschaft zusammen mit einem grafischen Objekt festlegen. Implementieren wir die gleiche Funktion für die Klassenmethoden — entweder schreiben wir einfach den Wert in die Eigenschaft des Objekts, oder, nachdem wir den Wert in die Eigenschaft geschrieben haben, zeichnen wir das gesamte Objekt neu, da die Änderung der Eigenschaft zu einer Änderung des Aussehens des SplitContainer-Steuerelements führen sollte.

Wir fügen den formalen Parametern der Methode das Flag hinzu, das angibt, dass ein Wert für die Objekteigenschaft gesetzt werden muss, und verschieben die Implementierung der Methode außerhalb des Klassenkörpers, indem wir sie hier entfernen:

//--- (1) set and (2) return the separator distance from the edge
   void              SetSplitterDistance(const int value,const bool only_prop);
   int               SplitterDistance(void)              const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE);   }
   
//--- (1) set and (2) return the separator non-removability flag
   void              SetSplitterFixed(const bool flag)         { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED,flag);             }
   bool              SplitterFixed(void)                 const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED);     }
   
//--- (1) set and (2) return the separator width
   void              SetSplitterWidth(const int value,const bool only_prop);
   int               SplitterWidth(void)                 const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH);      }
   
//--- (1) set and (2) return the separator location
   void              SetSplitterOrientation(const ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION value,const bool only_prop);
                                                                                                                     
   ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION SplitterOrientation(void) const
                       { return(ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION);      }


Damit das Klassenobjekt das Entfernen des Mauszeigers aus dem Trennbereich selbständig behandeln kann, müssen wir einen virtuellen Handler für das letzte Mausereignis hinzufügen. Wir deklarieren sie im öffentlichen Teil der Klasse:

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

//--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- Last mouse event handler
   virtual void      OnMouseEventPostProcessing(void);
   
//--- Constructor
                     CSplitContainer(const long chart_id,
                                     const int subwindow,
                                     const string descript,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h);
  };
//+------------------------------------------------------------------+


Im Klassenkonstruktor legen wir den Standardwert für die Eigenschaft feste Trennlinie auf „verschiebbar“ fest:

//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CSplitContainer::CSplitContainer(const long chart_id,
                                 const int subwindow,
                                 const string descript,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   this.SetBorderSizeAll(0);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetPaddingAll(0);
   this.SetMarginAll(3);
   this.SetOpacity(0,true);
   this.SetBackgroundColor(CLR_CANV_NULL,true);
   this.SetBackgroundColorMouseDown(CLR_CANV_NULL);
   this.SetBackgroundColorMouseOver(CLR_CANV_NULL);
   this.SetBorderColor(CLR_CANV_NULL,true);
   this.SetBorderColorMouseDown(CLR_CANV_NULL);
   this.SetBorderColorMouseOver(CLR_CANV_NULL);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetSplitterFixed(false);
   this.CreatePanels();
  }
//+------------------------------------------------------------------+

Nach der Erstellung des Objekts ist die Trennlinie standardmäßig verschiebbar. Wenn die Trennlinie später nicht mehr verschoben werden soll, setzen wir die Eigenschaft einfach mit derselben Methode auf true .


Die Methode zum Einstellen der Paneelparameter wurde geändert.
Unter wird nun für alle ausgeblendeten Bereiche die Größe des nicht ausgeblendeten Bereichs festgelegt. Das Paneel, das gleichzeitig sichtbar bleibt, wird gleich groß wie sein Container, d.h. für die gesamte Größe des SplitContainer-Objekts:

//+------------------------------------------------------------------+
//| Set the panel parameters                                         |
//+------------------------------------------------------------------+
bool CSplitContainer::SetsPanelParams(void)
  {
   switch(this.SplitterOrientation())
     {
      //--- The separator is positioned vertically
      case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL    :
        //--- If both panels are not collapsed,
        if(!this.Panel1Collapsed() && !this.Panel2Collapsed())
          {
           //--- set the panel1 coordinates and size
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.SplitterDistance();
           this.m_panel1_h=this.Height();
           //--- set the panel2 coordinates and size
           this.m_panel2_x=this.SplitterDistance()+this.SplitterWidth();
           this.m_panel2_y=0;
           this.m_panel2_w=this.Width()-this.m_panel2_x;
           this.m_panel2_h=this.Height();
           //--- write separator coordinates and size
           this.m_splitter_x=this.SplitterDistance();
           this.m_splitter_y=0;
           this.m_splitter_w=this.SplitterWidth();
           this.m_splitter_h=this.Height();
          }
        //--- If panel1 or panel2 is collapsed,
        else
          {
           //--- write the coordinates and sizes of panel1 and panel2
           this.m_panel2_x=this.m_panel1_x=0;
           this.m_panel2_y=this.m_panel1_y=0;
           this.m_panel2_w=this.m_panel1_w=this.Width();
           this.m_panel2_h=this.m_panel1_h=this.Height();
           //--- write separator coordinates and size
           this.m_splitter_x=0;
           this.m_splitter_y=0;
           this.m_splitter_w=this.SplitterWidth();
           this.m_splitter_h=this.Height();
          }
        break;
      //--- The separator is located horizontally
      case CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL  :
        //--- If both panels are not collapsed,
        if(!this.Panel1Collapsed() && !this.Panel2Collapsed())
          {
           //--- set the panel1 coordinates and size
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.Width();
           this.m_panel1_h=this.SplitterDistance();
           //--- set the panel2 coordinates and size
           this.m_panel2_x=0;
           this.m_panel2_y=this.SplitterDistance()+this.SplitterWidth();
           this.m_panel2_w=this.Width();
           this.m_panel2_h=this.Height()-this.m_panel2_y;
           //--- write separator coordinates and size
           this.m_splitter_x=0;
           this.m_splitter_y=this.SplitterDistance();
           this.m_splitter_w=this.Width();
           this.m_splitter_h=this.SplitterWidth();
          }
        //--- If panel1 or panel2 is collapsed,
        else
          {
           //--- write the coordinates and sizes of panel1 and panel2
           this.m_panel2_x=this.m_panel1_x=0;
           this.m_panel2_y=this.m_panel1_y=0;
           this.m_panel2_w=this.m_panel1_w=this.Width();
           this.m_panel2_h=this.m_panel1_h=this.Height();
           //--- write separator coordinates and size
           this.m_splitter_x=0;
           this.m_splitter_y=0;
           this.m_splitter_w=this.Width();
           this.m_splitter_h=this.SplitterWidth();
          }
        break;
      default:
        return false;
        break;
     }
//--- Set the coordinates and sizes of the control area equal to the properties set by the separator
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,this.m_splitter_x);
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,this.m_splitter_y);
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,this.m_splitter_w);
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,this.m_splitter_h);
   return true;
  }
//+------------------------------------------------------------------+


Die Methode, mit der das Flag für „zusammengeklappt“ für Feld 1 gesetzt wird:

//+------------------------------------------------------------------+
//| Set the flag of collapsed panel 1                                |
//+------------------------------------------------------------------+
void CSplitContainer::SetPanel1Collapsed(const int flag)
  {
//--- Set the flag, passed to the method, to the object property
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,flag);
   CSplitContainerPanel *p1=this.GetPanel1();
   CSplitContainerPanel *p2=this.GetPanel2();
   if(p1==NULL || p2==NULL)
      return;
//--- Set the parameters of the panels and the separator
   this.SetsPanelParams();
//--- If panel1 should be collapsed
   if(this.Panel1Collapsed())
     {
      //--- If panel1 is shifted to new coordinates and its size is changed, hide panel1
      if(p1.Move(this.CoordX()+this.m_panel1_x,this.CoordY()+this.m_panel1_y) && p1.Resize(this.m_panel1_w,this.m_panel1_h,false))
        {
         p1.SetCoordXRelative(p1.CoordX()-this.CoordX());
         p1.SetCoordYRelative(p1.CoordY()-this.CoordY());
         this.CollapsePanel1();
        }
      //--- set the expanded flag for panel2
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,false);
      //--- If panel2 is shifted to new coordinates and its size is changed, display panel2
      if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y) && p2.Resize(this.m_panel2_w,this.m_panel2_h,true))
        {
         p2.SetCoordXRelative(p2.CoordX()-this.CoordX());
         p2.SetCoordYRelative(p2.CoordY()-this.CoordY());
         this.ExpandPanel2();
        }
     }
//--- If panel1 should be expanded,
   else
     {
      //--- set the collapsed flag for panel2
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,true);
      //--- If panel2 is shifted to new coordinates and its size is changed, hide panel2
      if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y) && p2.Resize(this.m_panel2_w,this.m_panel2_h,false))
        {
         p2.SetCoordXRelative(p2.CoordX()-this.CoordX());
         p2.SetCoordYRelative(p2.CoordY()-this.CoordY());
         this.CollapsePanel2();
        }
      //--- If panel1 is shifted to new coordinates and its size is changed, display panel1
      if(p1.Move(this.CoordX()+this.m_panel1_x,this.CoordY()+this.m_panel1_y) && p1.Resize(this.m_panel1_w,this.m_panel1_h,true))
        {
         p1.SetCoordXRelative(p1.CoordX()-this.CoordX());
         p1.SetCoordYRelative(p1.CoordY()-this.CoordY());
         this.ExpandPanel1();
        }
     }
  }
//+------------------------------------------------------------------+

Die Logik der Methode ist im Code kommentiert. Je nach dem an die Methode übergebenen Flag-Wert wird entweder Panel1 ausgeblendet (das an die Methode übergebene Flag ist gleich true) und Panel2 auf die volle Größe des Containers erweitert. Wenn false an die Methode übergeben wird, wird panel2 ausgeblendet, während panel1 auf die volle Größe des Containers erweitert wird.


Die Methode, mit der das Flag für „zugeklappt“ für Paneel 2 gesetzt wird:

//+------------------------------------------------------------------+
//| Set the flag of collapsed panel 2                                |
//+------------------------------------------------------------------+
void CSplitContainer::SetPanel2Collapsed(const int flag)
  {
//--- Set the flag, passed to the method, to the object property
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,flag);
   CSplitContainerPanel *p1=this.GetPanel1();
   CSplitContainerPanel *p2=this.GetPanel2();
   if(p1==NULL || p2==NULL)
      return;
//--- Set the parameters of the panels and the separator
   this.SetsPanelParams();
//--- If panel2 should be collapsed,
   if(this.Panel2Collapsed())
     {
      //--- If panel2 is shifted to new coordinates and its size is changed, hide panel2
      if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y) && p2.Resize(this.m_panel2_w,this.m_panel2_h,false))
        {
         p2.SetCoordXRelative(p2.CoordX()-this.CoordX());
         p2.SetCoordYRelative(p2.CoordY()-this.CoordY());
         this.CollapsePanel2();
        }
      //--- set the expanded flag for panel1
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,false);
      //--- If panel1 is shifted to new coordinates and its size is changed, display panel1
      if(p1.Move(this.CoordX()+this.m_panel1_x,this.CoordY()+this.m_panel1_y) && p1.Resize(this.m_panel1_w,this.m_panel1_h,true))
        {
         p1.SetCoordXRelative(p1.CoordX()-this.CoordX());
         p1.SetCoordYRelative(p1.CoordY()-this.CoordY());
         this.ExpandPanel1();
        }
     }
//--- If panel2 should be expanded,
   else
     {
      //--- set the collapsed flag for panel1
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,true);
      //--- If panel1 is shifted to new coordinates and its size is changed, hide panel1
      if(p1.Move(this.CoordX()+this.m_panel1_x,this.CoordY()+this.m_panel1_y) && p1.Resize(this.m_panel1_w,this.m_panel1_h,false))
        {
         p1.SetCoordXRelative(p1.CoordX()-this.CoordX());
         p1.SetCoordYRelative(p1.CoordY()-this.CoordY());
         this.CollapsePanel1();
        }
      //--- If panel2 is shifted to new coordinates and its size is changed, display panel2
      if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y) && p2.Resize(this.m_panel2_w,this.m_panel2_h,true))
        {
         p2.SetCoordXRelative(p2.CoordX()-this.CoordX());
         p2.SetCoordYRelative(p2.CoordY()-this.CoordY());
         this.ExpandPanel2();
        }
     }
  }
//+------------------------------------------------------------------+

Die Logik der Methode ähnelt der Logik der obigen Methode, aber die Flags werden für Panel2 gesetzt, während Panel1 je nachdem, ob Panel2 ein- oder ausgeklappt ist, ausgeblendet/angezeigt wird.


Die Methode, die den Abstand der Trennlinie vom Rand festlegt:

//+------------------------------------------------------------------+
//| Set the separator distance from the edge                         |
//+------------------------------------------------------------------+
void CSplitContainer::SetSplitterDistance(const int value,const bool only_prop)
  {
//--- Set the value, passed to the method, to the object property
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,value);
//--- Depending on the direction of the separator (vertical or horizontal),
//--- set the values to the coordinates of the object control area
   switch(this.SplitterOrientation())
     {
      case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL :
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,this.SplitterDistance());
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,0);
        break;
      //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL
      default:
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,0);
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,this.SplitterDistance());
        break;
     }
//--- If only setting the property, leave
   if(only_prop)
      return;
//--- If there are no panels or separator, leave
   CSplitContainerPanel *p1=this.GetPanel1();
   CSplitContainerPanel *p2=this.GetPanel2();
   CSplitter *sp=this.GetSplitter();
   if(p1==NULL || p2==NULL || sp==NULL)
      return;
//--- Set the parameters of the panels and the separator
   this.SetsPanelParams();
//--- If the size of the separator object has been successfully changed
   if(sp.Resize(this.m_splitter_w,this.m_splitter_h,false))
     {
      //--- Shift the separator
      if(sp.Move(this.CoordX()+this.m_splitter_x,this.CoordY()+this.m_splitter_y))
        {
         //--- Set new relative separator coordinates
         sp.SetCoordXRelative(sp.CoordX()-this.CoordX());
         sp.SetCoordYRelative(sp.CoordY()-this.CoordY());
         //--- If panel 1 is resized successfully
         if(p1.Resize(this.m_panel1_w,this.m_panel1_h,true))
           {
            //--- If panel 2 coordinates are changed to new ones
            if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y,true))
              {
               //--- if panel 2 has been successfully resized,
               if(p2.Resize(this.m_panel2_w,this.m_panel2_h,true))
                 {
                  //--- set new relative coordinates of panel 2
                  p2.SetCoordXRelative(p2.CoordX()-this.CoordX());
                  p2.SetCoordYRelative(p2.CoordY()-this.CoordY());
                 }
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

Wenn das übergebene Flag only_prop (nur die Eigenschaft setzen) falsch ist, müssen wir neue Koordinaten und Größen für die Tafeln und das Trennzeichen festlegen und die Paneelen entsprechend dem neuen Wert für den Trennlinienabstand neu aufbauen, abgesehen vom Setzen eines neuen Werts für die Eigenschaft „Trennlinienabstand“.


Methode, die die Dicke der Trennlinie festlegt:

//+------------------------------------------------------------------+
//| Set the separator width                                          |
//+------------------------------------------------------------------+
void CSplitContainer::SetSplitterWidth(const int value,const bool only_prop)
  {
//--- Set the value, passed to the method, to the object property
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,value);
//--- Depending on the direction of the separator (vertical or horizontal),
//--- set the values to the object control area width and height
   switch(this.SplitterOrientation())
     {
      case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL :
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,this.SplitterWidth());
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,this.Height());
        break;
      //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL
      default:
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,this.Width());
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,this.SplitterWidth());
        break;
     }
//--- If only setting the property, leave
   if(only_prop)
      return;
//--- If there are no panels or separator, leave
   CSplitContainerPanel *p1=this.GetPanel1();
   CSplitContainerPanel *p2=this.GetPanel2();
   CSplitter *sp=this.GetSplitter();
   if(p1==NULL || p2==NULL || sp==NULL)
      return;
//--- Set the parameters of the panels and the separator
   this.SetsPanelParams();
//--- If the size of the separator object has been successfully changed
   if(sp.Resize(this.m_splitter_w,this.m_splitter_h,false))
     {
      //--- If the separator is shifted to new coordinates
      if(sp.Move(this.CoordX()+this.m_splitter_x,this.CoordY()+this.m_splitter_y))
        {
         //--- Set new relative separator coordinates
         sp.SetCoordXRelative(sp.CoordX()-this.CoordX());
         sp.SetCoordYRelative(sp.CoordY()-this.CoordY());
         //--- If panel 1 is resized successfully
         if(p1.Resize(this.m_panel1_w,this.m_panel1_h,true))
           {
            //--- If panel 2 coordinates are changed to new ones
            if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y,true))
              {
               //--- if panel 2 has been successfully resized,
               if(p2.Resize(this.m_panel2_w,this.m_panel2_h,true))
                 {
                  //--- set new relative coordinates of panel 2
                  p2.SetCoordXRelative(p2.CoordX()-this.CoordX());
                  p2.SetCoordYRelative(p2.CoordY()-this.CoordY());
                 }
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

Die Logik der aktuellen Methodenverfeinerung ähnelt der Logik der Verfeinerung der oben betrachteten Methode.


Die Methode, die die Position der Trennlinie festlegt:

//+------------------------------------------------------------------+
//| set the separator location                                       |
//+------------------------------------------------------------------+
void CSplitContainer::SetSplitterOrientation(const ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION value,const bool only_prop)
  {
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,value);
//--- If only setting the property, leave
   if(only_prop)
      return;
//--- If there are no panels or separator, leave
   CSplitContainerPanel *p1=this.GetPanel1();
   CSplitContainerPanel *p2=this.GetPanel2();
   CSplitter *sp=this.GetSplitter();
   if(p1==NULL || p2==NULL || sp==NULL)
      return;
//--- Set the parameters of the panels and the separator
   this.SetsPanelParams();
//--- If panel 1 is resized successfully
   if(p1.Resize(this.m_panel1_w,this.m_panel1_h,true))
     {
      //--- If panel 2 coordinates are changed to new ones
      if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y,true))
        {
         //--- if panel 2 has been successfully resized,
         if(p2.Resize(this.m_panel2_w,this.m_panel2_h,true))
           {
            //--- set new relative coordinates of panel 2
            p2.SetCoordXRelative(p2.CoordX()-this.CoordX());
            p2.SetCoordYRelative(p2.CoordY()-this.CoordY());
           }
        }
      //--- If the size of the separator object has been successfully changed, 
      //--- set new values of separator coordinates
      if(sp.Resize(this.m_splitter_w,this.m_splitter_h,false))
         this.SetSplitterDistance(this.SplitterDistance(),true);
     }
  }
//+------------------------------------------------------------------+

Die Logik der Methode ist identisch mit der Logik der oben genannten Methoden. Wir setzen zunächst die an die Methode übergebenen Werte auf die Objekteigenschaft. Wenn das Flag only_prop auf false gesetzt ist, werden die Parameter für die Felder und die Trennlinie gesetzt und die Position der Felder und der Trennlinie in Abhängigkeit von den eingestellten Eigenschaften ihrer Größen und Koordinaten neu angeordnet.


Da nun die Methoden, die die Eigenschaften des Objekts ändern, sofort die Position der Tafeln und des Trennlinie wiederherstellen können, ist die Ereignisbehandlung etwas kürzer geworden:

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

Wenn die Trennlinie fest ist, sollte das Ereignis „Element verschieben“ nicht behandelt werden, daher wird dieser Wert geprüft und die Ereignisbehandlung beendet, wenn die Trennlinie fest ist. Die Methode SetSplitterDistance() erfüllt hier eine Doppelfunktion: Sie setzt einen neuen Wert für die Trennkoordinaten und baut die Paneele des Objekts neu auf, da beim Aufruf der Methode das Flag only_prop auf false gesetzt wird.


Die Ereignisbehandlung von „Der Cursor befindet sich innerhalb des aktiven Bereichs, die Maustasten sind nicht angeklickt“ empfängt den Zeiger auf die Trennlinie und zeigt es schraffiert an. Wenn die Trennlinie des SplitContainer-Steuerelements fest ist, sollte das Trennlinien-Objekt nicht erscheinen.
Ganz am Anfang der Ereignisbehandlung fügen wir daher die folgende Prüfung ein: Wenn die Trennlinie fest ist, verlassen wir die Methode:

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


Bei allen grafischen Elementen können wir immer sehen, was das letzte Mausereignis für das Objekt war. Um dies zu erreichen, verfügt die Formularobjektklasse über die virtuelle Methode OnMouseEventPostProcessing(), die wir in abgeleiteten Klassen überschreiben können, wenn die Logik der Methode der Elternklasse für die Behandlung des letzten Mausereignisses nicht geeignet ist. Das ist genau das, was ich in dieser Klasse gemacht habe.

Behandlung des letzten Mausereignisses:

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

Wenn das letzte Ereignis das Entfernen des Mauszeigers aus dem Objektbereich ist, holen wir den Zeiger auf der Trennlinie, setzen das Nicht-Anzeigen-Flag, blenden die Trennlinie aus und setzen den aktuellen Mausstatus auf den vorherigen.
Wenn man nun die Maus vom SplitContainer-Steuerelement wegbewegen, wird die Trennlinie ausgeblendet. Später werde ich die Handhabung der Bewegung des Mauszeigers aus dem Trennbereich hinzufügen — um dies nicht in den Paneel-Objekten des SplitContainer-Steuerelements zu tun.


In der Datei \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh der Klasse für grafische Elemente ergänzen wir der Methode FormPostProcessing() für die Überprüfung des Objekt-Nicht-Anzeige-Flags, da die nicht anzuzeigenden Objekte auch nicht von der Ereignisbehandlung behandelt werden sollten:

//+------------------------------------------------------------------+
//| Post-processing of the former active form under the cursor       |
//+------------------------------------------------------------------+
void CGraphElementsCollection::FormPostProcessing(CForm *form,const int id, const long &lparam, const double &dparam, const string &sparam)
  {
//--- Get the main object the form is attached to
   CForm *main=form.GetMain();
   if(main==NULL)
      main=form;
//--- Get all the elements attached to the form
   CArrayObj *list=main.GetListElements();
   if(list==NULL)
      return;
   //--- In the loop by the list of received elements
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the pointer to the object
      CForm *obj=list.At(i);
      //--- if failed to get the pointer, move on to the next one in the list
      if(obj==NULL || !obj.IsVisible() || !obj.Enabled() || !obj.Displayed())
         continue;
      obj.OnMouseEventPostProcessing();
      //--- Create the list of interaction objects and get their number
      int count=obj.CreateListInteractObj();
      //--- In the loop by the obtained list
      for(int j=0;j<count;j++)
        {
         //--- get the next object
         CWinFormBase *elm=obj.GetInteractForm(j);
         if(elm==NULL || !elm.IsVisible() || !elm.Enabled() || !elm.Displayed())
            continue;

         if(elm.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)
           {
            CTabControl *tab_ctrl=elm;
            CForm *selected=tab_ctrl.SelectedTabPage();
            if(selected!=NULL)
               elm=selected;
           }

         //--- determine the location of the cursor relative to the object 
         //--- and call the mouse event handling method for the object
         elm.MouseFormState(id,lparam,dparam,sparam);
         elm.OnMouseEventPostProcessing();
        }
     }
   ::ChartRedraw(main.ChartID());
  }
//+------------------------------------------------------------------+

Das sind vorerst alle Verbesserungen. Lassen Sie uns die Ergebnisse überprüfen.


Test

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

Der EA erstellt das Paneel, auf dem TabControl aufgebaut ist. Wir erstellen auf jeder der ersten fünf Registerkarten ein SplitContainer-Steuerelement. Die Trennlinie ist bei geraden Indizes vertikal und bei ungeraden horizontal. In der dritten Registerkarte machen wir eine feste Trennlinie und in der vierten und fünften Registerkarte werden panel2 and panel1 ausgeblendet.

OnInit() des EAs sieht nun wie folgt aus:

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

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

Die Logik ist im Code vollständig kommentiert.

In der Schleife nach der Anzahl der Registerkarten für die Erstellung der SplitContainer-Steuerelemente erstellen wir für jede Registerkarte ein neues Objekt. Wir holen uns nach der Erstellung den Zeiger und setzen die neuen Parameter.

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



Wie Sie sehen, ändern alle neuen Eigenschaften, die dem Objekt nach seiner Erstellung zugewiesen werden, sein Aussehen korrekt.

Was die Unzulänglichkeiten anbelangt, so können wir einen unscharfen Auslöser für das Ausblenden des Trennungsobjekts sehen, nachdem der Mauszeiger davon wegbewegt wurde. Ich werde jedoch das Verhalten und die Anzeigelogik ändern, um der Verhaltenslogik in MS Visual Studio besser zu entsprechen. Dies wird es uns ermöglichen, dieses Thema zu vertiefen.


Was kommt als Nächstes?

Im nächsten Artikel werde ich meine Arbeit mit dem SplitContainer-Steuerelement 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. Schreiben Sie Ihre Fragen, Kommentare und Vorschläge im Kommentarteil.

Zurück zum Inhalt

*Vorherige Artikel in dieser Reihe:

 
DoEasy. Steuerung (Teil 13): Optimierung der Interaktion von WinForms-Objekten mit der Maus, Beginn der Entwicklung des WinForms-Objekts TabControl
DoEasy. Steuerung (Teil 14): Neuer Algorithmus zur Benennung von grafischen Elementen. Fortsetzung der Arbeit am TabControl WinForms Objekt
DoEasy. Steuerung (Teil 15): TabControl WinForms Objekt — mehrere Reihen von Registerkartenüberschriften, Methoden zur Behandlung von Registerkarten 
DoEasy. Steuerung (Teil 16): TabControl WinForms-Objekt — mehrere Reihen von Registerkarten-Kopfzeilen, Dehnung der Kopfzeilen zur Anpassung an den Container
DoEasy. Steuerung (Teil 17): Beschneiden unsichtbarer Objektteile, Hilfspfeiltasten WinForms-Objekte
DoEasy. Steuerung (Teil 18): Funktionalität für scrollende Registerkarten in TabControl
DoEasy. Steuerung (Teil 19): Verschieben der Registerkarten in TabControl, Ereignisse im WinForms-Objekt
DoEasy. Steuerung (Teil 20): SplitContainer WinForms-Objekt
DoEasy. Steuerung (Teil 21): SplitContainer-Steuerung. Paneel-Trennlinie