English 中文 Español Deutsch 日本語 Português
preview
DoEasy. Элементы управления (Часть 22): SplitContainer. Изменение свойств созданного объекта

DoEasy. Элементы управления (Часть 22): SplitContainer. Изменение свойств созданного объекта

MetaTrader 5Примеры | 14 октября 2022, 16:11
651 0
Artyom Trishkin
Artyom Trishkin

Содержание


Концепция

Элемент управления SplitContainer в библиотеке создаётся со значениями по умолчанию. Свойства объекта мы можем изменить после создания объекта, но его внешний вид при этом не изменится. Чтобы такого не происходило, необходимо после изменения свойства объекта перерисовать его с новыми значениями его свойств.

Сегодня будет небольшая статья, в которой мы доработаем методы установки свойств элемента управления так, чтобы все изменения его свойств могли сразу же изменять и его внешний вид.


Доработка классов библиотеки

В файле \MQL5\Include\DoEasy\Defines.mqh, в список возможных событий элементов управления WinForms впишем два новых события:

//+------------------------------------------------------------------+
//| Список возможных событий элементов управления WinForms           |
//+------------------------------------------------------------------+
enum ENUM_WF_CONTROL_EVENT
  {
   WF_CONTROL_EVENT_NO_EVENT = GRAPH_OBJ_EVENTS_NEXT_CODE,// Нет события
   WF_CONTROL_EVENT_CLICK,                            // Событие "Щелчок по элементу управления"
   WF_CONTROL_EVENT_CLICK_CANCEL,                     // Событие "Отмена щелчка по элементу управления"
   WF_CONTROL_EVENT_MOVING,                           // Событие "Перемещение элемента управления"
   WF_CONTROL_EVENT_STOP_MOVING,                      // Событие "Остановка перемещения элемента управления"
   WF_CONTROL_EVENT_TAB_SELECT,                       // Событие "Выбор вкладки элемента управления TabControl"
   WF_CONTROL_EVENT_CLICK_SCROLL_LEFT,                // Событие "Щелчок по кнопке влево элемента управления"
   WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT,               // Событие "Щелчок по кнопке влево элемента управления"
   WF_CONTROL_EVENT_CLICK_SCROLL_UP,                  // Событие "Щелчок по кнопке влево элемента управления"
   WF_CONTROL_EVENT_CLICK_SCROLL_DOWN,                // Событие "Щелчок по кнопке влево элемента управления"
                                     
  };
#define WF_CONTROL_EVENTS_NEXT_CODE (WF_CONTROL_EVENT_CLICK_SCROLL_DOWN+1)  // Код следующего события после последнего кода события графических элементов
//+------------------------------------------------------------------+

Некоторые элементы управления должны реагировать на их перемещение и отсылать события в программу или в родительский элемент управления, в составе которого они работают. Например, ползунок в области прокрутки при его захвате мышкой и смещении должен отправлять событие о своём перемещении и значения, на которые он был смещён. Точно так же разделитель в элементе управления SplitContainer должен реагировать на его перемещение.

В текущей его реализации он изменяет свой внешний вид при наведении курсора мышки на него, а при его перемещении отсылает событие в родительский элемент, который реагирует на это событие изменением размеров своих панелей. Впоследствии мы доработаем это поведение, и объект при наведении мышки будет очерчиваться прямоугольником, нарисованным пунктирной линией, а при его захвате мышкой — заполняться штрихованной областью. При отпускании мышки (завершении перемещения) разделитель будет принимать изначальный внешний вид.

Для того чтобы мы могли вовремя и правильно реагировать на такие события (перемещение элемента управления и завершение его перемещения) мы и добавили два новых события. Соответственно, из этого списка мы убрали самое последнее событие: WF_CONTROL_EVENT_SPLITTER_MOVE, так как теперь его можно заменить универсальным для всех элементов событием WF_CONTROL_EVENT_MOVING. А так как последним событием в списке теперь стало событие WF_CONTROL_EVENT_CLICK_SCROLL_DOWN, то именно его и впишем в расчёт значения кода следующего события.


Если запустить советник из прошлой статьи и начать двигать основную панель, то на элементе управления SplitContainer его разделитель виден не будет — это правильно — он должен быть скрыт пока на него не будет наведён курсор мышки. Но вот стоит объект-разделитель хотя бы раз сдвинуть, то после этого любое перемещение основной панели будет приводить к тому, что этот скрытый элемент управления будет отображаться.

Это ошибка логики, которую было сложно найти. Но теперь причина найдена и решение тоже — метод BringToTop() работает таким образом, что сначала скрывает объект, а затем его отображает, тем самым выводя его на передний план. Именно в этом методе скрытый объект-разделитель становился видимым, так как метод не учитывает состояние флага необходимости отрисовки элемента.

В файле \MQL5\Include\DoEasy\Objects\Graph\Form.mqh, в методе, устанавливающем объект выше всех, добавим проверку флага необходимости прорисовки элемента, и если объект не должен отображаться, то просто уйдём из метода:

//+------------------------------------------------------------------+
//| Устанавливает объект выше всех                                   |
//+------------------------------------------------------------------+
void CForm::BringToTop(void)
  {
//--- Если объект не должен отображаться - уходим
   if(!this.Displayed())
      return;
//--- Если стоит флаг использования тени
   if(this.m_shadow)
     {
      //--- Если объект тени создан - переносим его на передний план
      if(this.m_shadow_obj!=NULL)
         this.m_shadow_obj.BringToTop();
     }
//--- Переносим объект на передний план (объект станет выше тени)
   CGCnvElement::BringToTop();
//--- В цикле по всем привязанным объектам
   int total=this.m_list_elements.Total();
   for(int i=0;i<total;i++)
     {
      //--- получаем очередной объект из списка
      CGCnvElement *obj=this.m_list_elements.At(i);
      if(obj==NULL)
         continue;
      //--- и переносим его на передний план если объект должен отображаться
      if(!obj.Displayed())
         continue;
      obj.BringToTop();
     }
  }
//+------------------------------------------------------------------+

И такую же проверку добавим для списка всех прикреплённых объектов — будем пропускать те объекты, для которых флаг необходимости отрисовки снят — чтобы не выводить объект на передний план.

В обработчике последнего события мышки добавим проверку флага необходимости отрисовки объекта:

//+------------------------------------------------------------------+
//| Обработчик последнего события мышки                              |
//+------------------------------------------------------------------+
void CForm::OnMouseEventPostProcessing(void)
  {
   if(!this.IsVisible() || !this.Enabled() || !this.Displayed())
      return;
   ENUM_MOUSE_FORM_STATE state=this.GetMouseState();
   switch(state)
     {
      //--- Курсор за пределами формы, кнопки мышки не нажаты
      //---...
      //---...

Если ранее последнее событие мышки не обрабатывалось для скрытых и неактивных объектов, то сейчас в добавок не будут обрабатываться и те объекты, которые не должны быть отображены.


В классе объекта-панели элемента управления SplitContainer в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\SplitContainerPanel.mqh добавим проверку флага необходимости отрисовки объекта в методах очистки элемента:

//+------------------------------------------------------------------+
//| Очищает элемент с заполнением его цветом и непрозрачностью       |
//+------------------------------------------------------------------+
void CSplitContainerPanel::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
//--- Если элемент не должен показываться (скрыт внутри другого элемента управления) - уходим
   if(!this.Displayed())
      return;
//--- Закрашиваем элемент с указанным цветом и флагом необходимости перерисовки
   CGCnvElement::EraseNoCrop(colour,opacity,false);
//--- Если у объекта есть рамка - рисуем её
   if(this.BorderStyle()!=FRAME_STYLE_NONE)
      this.DrawFrame();
//--- Обновляем элемент с указанным флагом необходимости перерисовки
   this.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Очищает элемент заливкой градиентом                              |
//+------------------------------------------------------------------+
void CSplitContainerPanel::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false)
  {
//--- Если элемент не должен показываться (скрыт внутри другого элемента управления) - уходим
   if(!this.Displayed())
      return;
//--- Закрашиваем элемент с указанным массивом цветов и флагом необходимости перерисовки
   CGCnvElement::EraseNoCrop(colors,opacity,vgradient,cycle,false);
//--- Если у объекта есть рамка - рисуем её
   if(this.BorderStyle()!=FRAME_STYLE_NONE)
      this.DrawFrame();
//--- Обновляем элемент с указанным флагом необходимости перерисовки
   this.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Эти проверки тоже служат для предотвращения незапланированного отображения панели, если она скрыта (свёрнута) в элементе управления SplitContainer.

Если разделитель элемента управления SplitContainer имеет установленный флаг фиксированного разделителя, то он никак не должен взаимодействовать с мышкой. Соответственно, если курсор мышки наведён на панель (курсор ушёл с разделителя и зашёл на панель), то обрабатывать такое событие для фиксированного разделителя не нужно.

В обработчике события "Курсор в пределах активной области, кнопки мышки не нажаты" добавим проверку флага фиксированного разделителя:

//+------------------------------------------------------------------+
//| Обработчик события Курсор в пределах активной области,           |
//| кнопки мышки не нажаты                                           |
//+------------------------------------------------------------------+
void CSplitContainerPanel::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Получаем указатель на базовый объект
   CSplitContainer *base=this.GetBase();
//--- Если базовый объект не получен, или разделитель неперемещаемый - уходим
   if(base==NULL || base.SplitterFixed())
      return;
//--- Из базового объекта получаем указатель на объект-разделитель
   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(splitter.Displayed())
     {
      //--- Выключаем отображение разделителя и скрываем его
      splitter.SetDisplayed(false);
      splitter.Hide();
     }
  }
//+------------------------------------------------------------------+

Такая проверка, по сути, просто ограничивает нас от лишних и ненужных действий — нет необходимости получать объект-разделитель и скрывать его, если он и так изначально скрыт.

Все основные изменения и доработки будут проводиться в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\SplitContainer.mqh WinForms-объекта SplitContainer.

В приватной секции класса объявим новые методы для сворачивания/разворачивания панелей элемента:

//--- Устанавливает параметры панелям
   bool              SetsPanelParams(void);

//--- (1) Сворачивает, (2) разворачивает панель 1
   void              CollapsePanel1(void);
   void              ExpandPanel1(void);
//--- (1) Сворачивает, (2) разворачивает панель 2
   void              CollapsePanel2(void);
   void              ExpandPanel2(void);

public:


За пределами тела класса напишем реализацию объявленных методов.

Свёрнутая панель будет скрываться, а для её свойства необходимости отображения будет устанавливаться значение false:

//+------------------------------------------------------------------+
//| Сворачивает панель 1                                             |
//+------------------------------------------------------------------+
void CSplitContainer::CollapsePanel1(void)
  {
   CSplitContainerPanel *panel=this.GetPanel1();
   if(panel==NULL)
      return;
   panel.SetDisplayed(false);
   panel.Hide();
  }
//+------------------------------------------------------------------+
//| Сворачивает панель 2                                             |
//+------------------------------------------------------------------+
void CSplitContainer::CollapsePanel2(void)
  {
   CSplitContainerPanel *panel=this.GetPanel2();
   if(panel==NULL)
      return;
   panel.SetDisplayed(false);
   panel.Hide();
  }
//+------------------------------------------------------------------+


Для развёрнутой панели будем устанавливать необходимость её отображения, показывать панель и выводить её на передний план:

//+------------------------------------------------------------------+
//| Разворачивает панель 1                                           |
//+------------------------------------------------------------------+
void CSplitContainer::ExpandPanel1(void)
  {
   CSplitContainerPanel *panel=this.GetPanel1();
   if(panel==NULL)
      return;
   panel.SetDisplayed(true);
   panel.Show();
   panel.BringToTop();
  }
//+------------------------------------------------------------------+
//| Разворачивает панель 2                                           |
//+------------------------------------------------------------------+
void CSplitContainer::ExpandPanel2(void)
  {
   CSplitContainerPanel *panel=this.GetPanel2();
   if(panel==NULL)
      return;
   panel.SetDisplayed(true);
   panel.Show();
   panel.BringToTop();
  }
//+------------------------------------------------------------------+


Некоторые методы установки свойств объектам в библиотеке могут либо просто устанавливать значение в свойство, либо установить значение в свойство и установить его в графический объект. Для методов данного класса тоже сделаем такую возможность — либо просто записать значение в свойство объекта, либо после записи значения в свойство перерисовать полностью объект, так как изменение свойства должно привести к изменению внешнего вида элемента управления SplitContainer.

Допишем в формальные параметры методов флаг, указывающий что нужно только установить значение в свойство объекта, и перенесём реализацию метода за пределы тела класса, убрав её здесь:

//--- (1) устанавливает, (2) возвращает дистанцию разделителя от края
   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) устанавливает, (2) возвращает флаг неперемещаемости разделителя
   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) устанавливает, (2) возвращает толщину разделителя
   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) устанавливает, (2) возвращает расположение разделителя
   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);      }


Чтобы объект класса мог самостоятельно обработать увод курсора мышки из области разделителя, нам нужно добавить виртуальный обработчик последнего события мышки. Объявим его в публичной секции класса:

//--- Обработчик событий
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- Обработчик события  Курсор в пределах активной области, кнопки мышки не нажаты
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- Обработчик последнего события мышки
   virtual void      OnMouseEventPostProcessing(void);
   
//--- Конструктор
                     CSplitContainer(const long chart_id,
                                     const int subwindow,
                                     const string descript,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h);
  };
//+------------------------------------------------------------------+


В конструкторе класса установим значение по умолчанию для свойства фиксированного разделителя как "перемещаемый":

//+------------------------------------------------------------------+
//| Конструктор с указанием идентификатора чарта и подокна           |
//+------------------------------------------------------------------+
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();
  }
//+------------------------------------------------------------------+

После создания объекта, по умолчанию разделитель будет перемещаемым. При необходимости позже сделать разделитель не перемещаемым, нужно просто установить это свойство в значение true при помощи этого же метода.


Метод, устанавливающий параметры панелям, претерпел изменения.
Теперь для всех скрытых панелей будут устанавливаться размеры, равные размерам не скрытой панели. А панель, которая при этом остаётся видимой, становится равной размерам своего контейнера — т.е. на всю величину объекта SplitContainer:

//+------------------------------------------------------------------+
//| Устанавливает параметры панелям                                  |
//+------------------------------------------------------------------+
bool CSplitContainer::SetsPanelParams(void)
  {
   switch(this.SplitterOrientation())
     {
      //--- Разделитель расположен вертикально
      case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL    :
        //--- Если обе панели не свёрнуты
        if(!this.Panel1Collapsed() && !this.Panel2Collapsed())
          {
           //--- записываем координаты и размеры панели1
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.SplitterDistance();
           this.m_panel1_h=this.Height();
           //--- записываем координаты и размеры панели2
           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();
           //--- записываем координаты и размеры разделителя
           this.m_splitter_x=this.SplitterDistance();
           this.m_splitter_y=0;
           this.m_splitter_w=this.SplitterWidth();
           this.m_splitter_h=this.Height();
          }
        //--- Если свёрнута панель1 или панель2
        else
          {
           //--- записываем координаты и размеры панели1 и панели2
           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();
           //--- записываем координаты и размеры разделителя
           this.m_splitter_x=0;
           this.m_splitter_y=0;
           this.m_splitter_w=this.SplitterWidth();
           this.m_splitter_h=this.Height();
          }
        break;
      //--- Разделитель расположен горизонтально
      case CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL  :
        //--- Если обе панели не свёрнуты
        if(!this.Panel1Collapsed() && !this.Panel2Collapsed())
          {
           //--- записываем координаты и размеры панели1
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.Width();
           this.m_panel1_h=this.SplitterDistance();
           //--- записываем координаты и размеры панели2
           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;
           //--- записываем координаты и размеры разделителя
           this.m_splitter_x=0;
           this.m_splitter_y=this.SplitterDistance();
           this.m_splitter_w=this.Width();
           this.m_splitter_h=this.SplitterWidth();
          }
        //--- Если свёрнута панель1 или панель2
        else
          {
           //--- записываем координаты и размеры панели1 и панели2
           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();
           //--- записываем координаты и размеры разделителя
           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;
     }
//--- Устанавливаем координаты и размеры области управления, равными свойствам, установленным разделителю
   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;
  }
//+------------------------------------------------------------------+


Метод, устанавливающий флаг свёрнутости панели 1:

//+------------------------------------------------------------------+
//| Устанавливает флаг свёрнутости панели 1                          |
//+------------------------------------------------------------------+
void CSplitContainer::SetPanel1Collapsed(const int flag)
  {
//--- Записываем в свойство объекта переданный в метод флаг
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,flag);
   CSplitContainerPanel *p1=this.GetPanel1();
   CSplitContainerPanel *p2=this.GetPanel2();
   if(p1==NULL || p2==NULL)
      return;
//--- Устанавливаем параметры панелей и разделителя
   this.SetsPanelParams();
//--- Если панель1 должна быть свёрнута
   if(this.Panel1Collapsed())
     {
      //--- Если панель1 смещена на новые координаты и её размер изменён - скрываем панель1
      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();
        }
      //--- устанавливаем для панели2 флаг того, что она развёрнута
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,false);
      //--- Если панель2 смещена на новые координаты и её размер изменён - отображаем панель2
      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();
        }
     }
//--- Если панель1 должна быть развёрнута
   else
     {
      //--- устанавливаем для панели2 флаг того, что она свёрнута
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,true);
      //--- Если панель2 смещена на новые координаты и её размер изменён - скрываем панель2
      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();
        }
      //--- Если панель1 смещена на новые координаты и её размер изменён - отображаем панель1
      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();
        }
     }
  }
//+------------------------------------------------------------------+

Логика метода прокомментирована в коде. В зависимости от переданного в метод значения флага, либо скрываем панель1 (переданный в метод флаг имеет значение true) и отображаем панель2, разворачивая её на всю величину контейнера. Если в метод передан флаг false, то скрываем панель2, а панель1 разворачиваем на всю величину контейнера.


Метод, устанавливающий флаг свёрнутости панели 2:

//+------------------------------------------------------------------+
//| Устанавливает флаг свёрнутости панели 2                          |
//+------------------------------------------------------------------+
void CSplitContainer::SetPanel2Collapsed(const int flag)
  {
//--- Записываем в свойство объекта переданный в метод флаг
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,flag);
   CSplitContainerPanel *p1=this.GetPanel1();
   CSplitContainerPanel *p2=this.GetPanel2();
   if(p1==NULL || p2==NULL)
      return;
//--- Устанавливаем параметры панелей и разделителя
   this.SetsPanelParams();
//--- Если панель2 должна быть свёрнута
   if(this.Panel2Collapsed())
     {
      //--- Если панель2 смещена на новые координаты и её размер изменён - скрываем панель2
      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();
        }
      //--- устанавливаем для панели1 флаг того, что она развёрнута
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,false);
      //--- Если панель1 смещена на новые координаты и её размер изменён - отображаем панель1
      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();
        }
     }
//--- Если панель2 должна быть развёрнута
   else
     {
      //--- устанавливаем для панели1 флаг того, что она свёрнута
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,true);
      //--- Если панель1 смещена на новые координаты и её размер изменён - скрываем панель1
      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();
        }
      //--- Если панель2 смещена на новые координаты и её размер изменён - отображаем панель2
      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();
        }
     }
  }
//+------------------------------------------------------------------+

Логика метода аналогична логике вышерассмотренного метода, но флаги устанавливаются для панели2, а панель1 скрывается/отображается в соответствии с тем свёрнута или развёрнута панель2.


Метод, устанавливающий дистанцию разделителя от края:

//+------------------------------------------------------------------+
//| Устанавливает дистанцию разделителя от края                      |
//+------------------------------------------------------------------+
void CSplitContainer::SetSplitterDistance(const int value,const bool only_prop)
  {
//--- Записываем в свойство объекта переданное в метод значение
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,value);
//--- В зависимости от ориентации разделителя (вертикально или горизонтально)
//--- записываем значения в координаты области управления объекта
   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_prop)
      return;
//--- Если панелей или разделителя нет - уходим
   CSplitContainerPanel *p1=this.GetPanel1();
   CSplitContainerPanel *p2=this.GetPanel2();
   CSplitter *sp=this.GetSplitter();
   if(p1==NULL || p2==NULL || sp==NULL)
      return;
//--- Устанавливаем параметры панелей и разделителя
   this.SetsPanelParams();
//--- Если размеры объекта-разделителя успешно изменены
   if(sp.Resize(this.m_splitter_w,this.m_splitter_h,false))
     {
      //--- Смещаем разделитель
      if(sp.Move(this.CoordX()+this.m_splitter_x,this.CoordY()+this.m_splitter_y))
        {
         //--- Устанавливаем новые относительные координаты разделителя
         sp.SetCoordXRelative(sp.CoordX()-this.CoordX());
         sp.SetCoordYRelative(sp.CoordY()-this.CoordY());
         //--- Если размеры панели 1 успешно изменены
         if(p1.Resize(this.m_panel1_w,this.m_panel1_h,true))
           {
            //--- Если координаты панели 2 изменены на новые
            if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y,true))
              {
               //--- если размеры панели 2 успешно изменены
               if(p2.Resize(this.m_panel2_w,this.m_panel2_h,true))
                 {
                  //--- устанавливаем новые относительные координаты панели 2
                  p2.SetCoordXRelative(p2.CoordX()-this.CoordX());
                  p2.SetCoordYRelative(p2.CoordY()-this.CoordY());
                 }
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

Доработка метода заключается в том, что если передан флаг only_prop (только установить свойство) со значением false, то кроме установки нового значения в свойство "дистанция разделителя", нам нужно установить новые значения координат и размеров для панелей и разделителя и перестроить панели в соответствии с новым значением дистанции разделителя.


Метод, устанавливающий толщину разделителя:

//+------------------------------------------------------------------+
//| Устанавливает толщину разделителя                                |
//+------------------------------------------------------------------+
void CSplitContainer::SetSplitterWidth(const int value,const bool only_prop)
  {
//--- Записываем в свойство объекта переданное в метод значение
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,value);
//--- В зависимости от ориентации разделителя (вертикально или горизонтально)
//--- записываем значения в свойства ширины и высоты области управления объекта
   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_prop)
      return;
//--- Если панелей или разделителя нет - уходим
   CSplitContainerPanel *p1=this.GetPanel1();
   CSplitContainerPanel *p2=this.GetPanel2();
   CSplitter *sp=this.GetSplitter();
   if(p1==NULL || p2==NULL || sp==NULL)
      return;
//--- Устанавливаем параметры панелей и разделителя
   this.SetsPanelParams();
//--- Если размеры объекта-разделителя успешно изменены
   if(sp.Resize(this.m_splitter_w,this.m_splitter_h,false))
     {
      //--- Если разделитель смещён на новые координаты
      if(sp.Move(this.CoordX()+this.m_splitter_x,this.CoordY()+this.m_splitter_y))
        {
         //--- Устанавливаем новые относительные координаты разделителя
         sp.SetCoordXRelative(sp.CoordX()-this.CoordX());
         sp.SetCoordYRelative(sp.CoordY()-this.CoordY());
         //--- Если размеры панели 1 успешно изменены
         if(p1.Resize(this.m_panel1_w,this.m_panel1_h,true))
           {
            //--- Если координаты панели 2 изменены на новые
            if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y,true))
              {
               //--- если размеры панели 2 успешно изменены
               if(p2.Resize(this.m_panel2_w,this.m_panel2_h,true))
                 {
                  //--- устанавливаем новые относительные координаты панели 2
                  p2.SetCoordXRelative(p2.CoordX()-this.CoordX());
                  p2.SetCoordYRelative(p2.CoordY()-this.CoordY());
                 }
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

Логика доработки метода аналогична логике доработки вышерассмотренного метода.


Метод, устанавливающий расположение разделителя:

//+------------------------------------------------------------------+
//| устанавливает расположение разделителя                           |
//+------------------------------------------------------------------+
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_prop)
      return;
//--- Если панелей или разделителя нет - уходим
   CSplitContainerPanel *p1=this.GetPanel1();
   CSplitContainerPanel *p2=this.GetPanel2();
   CSplitter *sp=this.GetSplitter();
   if(p1==NULL || p2==NULL || sp==NULL)
      return;
//--- Устанавливаем параметры панелей и разделителя
   this.SetsPanelParams();
//--- Если размеры панели 1 успешно изменены
   if(p1.Resize(this.m_panel1_w,this.m_panel1_h,true))
     {
      //--- Если координаты панели 2 изменены на новые
      if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y,true))
        {
         //--- если размеры панели 2 успешно изменены
         if(p2.Resize(this.m_panel2_w,this.m_panel2_h,true))
           {
            //--- устанавливаем новые относительные координаты панели 2
            p2.SetCoordXRelative(p2.CoordX()-this.CoordX());
            p2.SetCoordYRelative(p2.CoordY()-this.CoordY());
           }
        }
      //--- Если размеры объекта-разделителя успешно изменены - 
      //--- устанавливаем новые значения координат разделителя
      if(sp.Resize(this.m_splitter_w,this.m_splitter_h,false))
         this.SetSplitterDistance(this.SplitterDistance(),true);
     }
  }
//+------------------------------------------------------------------+

Логика метода идентична логике вышерассмотренных методов. Сначала устанавливаем в свойство объекта переданное в метод значение, а затем, если флаг only_prop имеет значение false, устанавливаем параметры панелей и разделителя и перестраиваем расположение панелей и разделителя в зависимости от установленных свойств их размеров и координат.


Так как теперь методы, изменяющие свойства объекта, могут сразу же и перестраивать расположение панелей и разделителя, то обработчик событий стал немного короче:

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CSplitContainer::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Корректируем смещение по Y для подокна
   CGCnvElement::OnChartEvent(id,lparam,dparam,sparam);
//--- Если идентификатор события - перемещение разделителя
   if(id==WF_CONTROL_EVENT_MOVING)
     {
      //--- Получаем указатель на объект-разделитель
      CSplitter *splitter=this.GetSplitter();
      if(splitter==NULL || this.SplitterFixed())
         return;
      //--- Объявляем переменные для координат разделителя
      int x=(int)lparam;
      int y=(int)dparam;
      //--- В зависимости от ориентации разделителя
      switch(this.SplitterOrientation())
        {
         //--- вертикальное положение разделителя
         case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL :
           //--- Устанавливаем координату Y равной координате Y элемента управления
           y=this.CoordY();
           //--- Корректируем координату X так, чтобы разделитель не выходил за пределы элемента управления
           //--- с учётом получающейся в итоге минимальной ширины панелей
           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
         //--- горизонтальное положение разделителя
         default:
           //--- Устанавливаем координату X равной координате X элемента управления
           x=this.CoordX();
           //--- Корректируем координату Y так, чтобы разделитель не выходил за пределы элемента управления
           //--- с учётом получающейся в итоге минимальной высоты панелей
           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(splitter.Move(x,y,true))
        {
         //--- устанавливаем разделителю его относительные координаты
         splitter.SetCoordXRelative(splitter.CoordX()-this.CoordX());
         splitter.SetCoordYRelative(splitter.CoordY()-this.CoordY());
         //--- В зависимости от ориентации разделителя устанавливаем его новые координаты
         this.SetSplitterDistance(!this.SplitterOrientation() ? splitter.CoordX()-this.CoordX() : splitter.CoordY()-this.CoordY(),false);
        }
     }
  }
//+------------------------------------------------------------------+
Если разделитель фиксированный, то событие перемещения элемента не должно обрабатываться, поэтому это значение проверяется и осуществляется выход из обработчика, если разделитель фиксированный. Метод SetSplitterDistance() здесь выполняет двойную функцию: устанавливает новое значение координат разделителя и перестраивает панели объекта, так как флаг only_prop при вызове метода указан как false.


Обработчик события "Курсор в пределах активной области, кнопки мышки не нажаты" получает указатель на объект-разделитель и отображает его в заштрихованном виде. Если же разделитель элемента управления SplitContainer фиксированный, то объект-разделитель появляться не должен.
Поэтому в самом начале обработчика добавим такую проверку: если разделитель фиксированный, то уходим из метода:

//+------------------------------------------------------------------+
//| Обработчик события Курсор в пределах активной области,           |
//| кнопки мышки не нажаты                                           |
//+------------------------------------------------------------------+
void CSplitContainer::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Если разделитель неперемещаемый - уходим
   if(this.SplitterFixed())
      return;
//--- Получаем указатель на разделитель
   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(!splitter.Displayed())
     {
      //--- Включаем отображение разделителя, показываем и перерисовываем его
      splitter.SetDisplayed(true);
      splitter.Show();
      splitter.Redraw(true);
     }
  }
//+------------------------------------------------------------------+


Во всех графических элементах мы всегда можем посмотреть каким было последнее событие мышки для объекта. Для этого в классе объекта-формы есть виртуальный метод OnMouseEventPostProcessing(), который мы можем переопределить в производных классах в случае, если логика метода родительского класса не подходит для обработки последнего события мышки, что мы и сделали в этом классе.

Обработчик последнего события мышки:

//+------------------------------------------------------------------+
//| Обработчик последнего события мышки                              |
//+------------------------------------------------------------------+
void CSplitContainer::OnMouseEventPostProcessing(void)
  {
   if(!this.IsVisible() || !this.Enabled() || !this.Displayed())
      return;
   ENUM_MOUSE_FORM_STATE state=this.GetMouseState();
   switch(state)
     {
      //--- Курсор за пределами формы, кнопки мышки не нажаты
      //--- Курсор за пределами формы, нажата кнопка мышки (любая)
      //--- Курсор за пределами формы, прокручивается колёсико мышки
      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)
          {
            //--- Получаем указатель на разделитель
            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;
      //--- Курсор в пределах формы, кнопки мышки не нажаты
      //--- Курсор в пределах формы, нажата кнопка мышки (любая)
      //--- Курсор в пределах формы, прокручивается колёсико мышки
      //--- Курсор в пределах активной области, кнопки мышки не нажаты
      //--- Курсор в пределах активной области, нажата кнопка мышки (любая)
      //--- Курсор в пределах активной области, прокручивается колёсико мышки
      //--- Курсор в пределах активной области, отжата кнопка мышки (левая)
      //--- Курсор в пределах области прокрутки окна, кнопки мышки не нажаты
      //--- Курсор в пределах области прокрутки окна, нажата кнопка мышки (любая)
      //--- Курсор в пределах области прокрутки окна, прокручивается колёсико мышки
      //--- Курсор в пределах области изменения размеров окна, кнопки мышки не нажаты
      //--- Курсор в пределах области изменения размеров окна, нажата кнопка мышки (любая)
      //--- Курсор в пределах области изменения размеров окна, прокручивается колёсико мышки
      //--- Курсор в пределах области резделителя окна, кнопки мышки не нажаты
      //--- Курсор в пределах области резделителя окна, нажата кнопка мышки (любая)
      //--- Курсор в пределах области резделителя окна, прокручивается колёсико мышки
      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;
     }
  }
//+------------------------------------------------------------------+

Если последнее событие — увод курсора мышки с области объекта, то получаем указатель на объект-разделитель, устанавливаем ему флаг неотображения, скрываем разделитель и устанавливаем текущее состояние мышки как прошлое.
Теперь при уводе мышки с элемента управления SplitContainer, его объект-разделитель будет скрываться. В последствии мы добавим сюда и обработку увода курсора мышки с области разделителя — чтобы не делать её в объектах-панелях элемента управления SplitContainer.


В файле \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh класса-коллекции графических элементов в метод FormPostProcessing() добавим проверку флага неотображения объекта, так как объекты, которые не должны отображаться, не должны и обрабатываться этим обработчиком во избежание их нештатного отображения:

//+------------------------------------------------------------------+
//| Постобработка бывшей активной формы под курсором                 |
//+------------------------------------------------------------------+
void CGraphElementsCollection::FormPostProcessing(CForm *form,const int id, const long &lparam, const double &dparam, const string &sparam)
  {
//--- Получаем главный объект, к которому прикреплена форма
   CForm *main=form.GetMain();
   if(main==NULL)
      main=form;
//--- Получаем все элементы, прикреплённые к форме
   CArrayObj *list=main.GetListElements();
   if(list==NULL)
      return;
   //--- В цикле по списку полученных элементов
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      //--- получаем указатель на объект
      CForm *obj=list.At(i);
      //--- если указатель получить не удалось - идём к следующему объекту в списке
      if(obj==NULL || !obj.IsVisible() || !obj.Enabled() || !obj.Displayed())
         continue;
      obj.OnMouseEventPostProcessing();
      //--- Создаём список объектов взаимодействия объекта и получаем их количество
      int count=obj.CreateListInteractObj();
      //--- В цикле по полученному списку
      for(int j=0;j<count;j++)
        {
         //--- получаем очередной объект
         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;
           }

         //--- определяем расположение курсора относительно объекта 
         //--- и вызываем для объекта метод обработки событий мышки
         elm.MouseFormState(id,lparam,dparam,sparam);
         elm.OnMouseEventPostProcessing();
        }
     }
   ::ChartRedraw(main.ChartID());
  }
//+------------------------------------------------------------------+

Это все доработки на сегодня. Проверим что у нас получилось.


Тестирование

Для теста возмём советник из прошлой статьи и сохраним его в новой папке \MQL5\Experts\TestDoEasy\Part122\ под новым именем TestDoEasy122.mq5.

Как будем тестировать. У нас в советнике создаётся панель, на которой строится элемент управления TabControl. В его первых пяти вкладках создадим на каждой по элементу управления SplitContainer. На чётных индексах вкладок разделитель будет вертикальным, на нечётных — горизонтальным. На третьей вкладке сделаем разделитель фиксированным, а на четвёртой и пятой вкладках сделаем свёрнутой панель2 и панель1 соответственно.

Обработчик OnInit() советника теперь будет таким:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Установка глобальных переменных советника
   ArrayResize(array_clr,2);        // Массив цветов градиентной заливки
   array_clr[0]=C'26,100,128';      // Исходный ≈Тёмно-лазурный цвет
   array_clr[1]=C'35,133,169';      // Осветлённый исходный цвет
//--- Создадим массив с текущим символом и установим его для использования в библиотеке
   string array[1]={Symbol()};
   engine.SetUsedSymbols(array);
   //--- Создадим объект-таймсерию для текущего символа и периода и выведем его описание в журнал
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(false); // Краткие описания

//--- Создадим требуемое количество объектов WinForms Panel
   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,"Описание панели: ",pnl.Description(),", Тип и имя: ",pnl.TypeElementDescription()," ",pnl.Name());
         //--- Установим значение Padding равным 4
         pnl.SetPaddingAll(3);
         //--- Установим флаги перемещаемости, автоизменения размеров и режим автоизменения из входных параметров
         pnl.SetMovable(InpMovable);
         pnl.SetAutoSize(InpAutoSize,false);
         pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false);
   
         //--- Создадим элемент управления 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"));
            //--- Создадим на каждой вкладке текстовую метку с описанием вкладки
            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;
               //--- Если это самая первая вкладка, то текста не будет
               label.SetText(j<5 ? "" : "TabPage"+string(j+1));
              }
            for(int n=0;n<5;n++)
              {
               //--- Создадим на каждой вкладке элемент управления SplitContainer
               tc.CreateNewElement(n,GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,10,10,tc.Width()-22,tc.GetTabField(0).Height()-22,clrNONE,255,true,false);
               //--- Получим элемент управления SplitContainer с каждой вкладки
               CSplitContainer *split_container=tc.GetTabElementByType(n,GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,0);
               if(split_container!=NULL)
                 {
                  //--- Для каждой чётной вкладки разделитель будет вертикальным, для нечётной - горизонтальным
                  split_container.SetSplitterOrientation(n%2==0 ? CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL : CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL,true);
                  //--- Дистанция разделителя на каждой вкладке будет 50 пикселей
                  split_container.SetSplitterDistance(50,true);
                  //--- Ширина разделителя на каждой последующей вкладке будет увеличиваться на 2 пикселя
                  split_container.SetSplitterWidth(4+2*n,false);
                  //--- Для вкладки с индексом 2 сделаем фиксированный разделитель, на остальных - перемещаемый
                  split_container.SetSplitterFixed(n==2 ? true : false);
                  //--- Для вкладки с индексом 3 вторая панель будет в свёрнутом состоянии (видна только первая)
                  if(n==3)
                     split_container.SetPanel2Collapsed(true);
                  //--- Для вкладки с индексом 4 первая панель будет в свёрнутом состоянии (видна только вторая)
                  if(n==4)
                     split_container.SetPanel1Collapsed(true);
                  //--- На каждой из панелей элемента ...
                  for(int j=0;j<2;j++)
                    {
                     CSplitContainerPanel *panel=split_container.GetPanel(j);
                     if(panel==NULL)
                        continue;
                     //--- ... создадим текстовую метку с названием панели
                     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));
                       }
                    }
                 }
              }
           }
        }
     }
//--- Отобразим и перерисуем все созданные панели
   for(int i=0;i<1;i++)
     {
      pnl=engine.GetWFPanelByName("Panel"+(string)i);
      if(pnl!=NULL)
        {
         pnl.Show();
         pnl.Redraw(true);
        }
     }
        
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Логика обработчика полностью прокомментирована в коде.

В цикле по количеству вкладок, на которых нужно построить элементы управления SplitContainer, создаём на каждой вкладке новый объект, а после его создания получаем на него указатель и устанавливаем ему новые параметры.

Скомпилируем советник и запустим его на графике:



Как видим, все новые свойства, установленные объекту уже после его создания, корректно вносят изменения в его внешний вид.

Из недостатков в первую очередь заметно нечёткое срабатывание сокрытия объекта-разделителя после увода курсора мышки с него. Но мы будем менять логику его поведения и отображения, чтобы больше соответствовать его логике поведения в MS Visual Studio, и вот уже тогда и будем разбираться в проблеме.


Что дальше

В следующей статье продолжим работу с элементом управления SplitContainer.

Ниже прикреплены все файлы текущей версии библиотеки, файлы тестового советника и индикатора контроля событий графиков для MQL5. Их можно скачать и протестировать всё самостоятельно. При возникновении вопросов, замечаний и пожеланий вы можете озвучить их в комментариях к статье.

К содержанию

*Статьи этой серии:

 
DoEasy. Элементы управления (Часть 13): Оптимизация взаимодействия WinForms-объектов с мышкой, начало разработки WinForms-объекта TabControl
DoEasy. Элементы управления (Часть 14): Новый алгоритм именования графических элементов. Продолжаем работу над WinForms-объектом TabControl
DoEasy. Элементы управления (Часть 15): WinForms-объект TabControl — несколько рядов заголовков вкладок, методы работы с вкладками 
DoEasy. Элементы управления (Часть 16): WinForms-объект TabControl — несколько рядов заголовков вкладок, режим растягивания заголовков под размеры контейнера
DoEasy. Элементы управления (Часть 17): Отсечение невидимых участков объектов, вспомогательные WinForms-объекты кнопки со стрелками
DoEasy. Элементы управления (Часть 18): Готовим функционал для прокрутки вкладок в TabControl
DoEasy. Элементы управления (Часть 19): Прокрутка вкладок в элементе TabControl, события WinForms-объектов
DoEasy. Элементы управления (Часть 20): WinForms-объект SplitContainer
DoEasy. Элементы управления (Часть 21): Элемент управления SplitContainer. Разделитель панелей



Прикрепленные файлы |
MQL5.zip (4567.48 KB)
Разработка торгового советника с нуля (Часть 25): Обеспечиваем надежность системы (II) Разработка торгового советника с нуля (Часть 25): Обеспечиваем надежность системы (II)
В этой статье мы сделаем финальный рывок к производительности советника... так что будьте готовы к долгому чтению. Чтобы сделать наш советник надежным, мы сначала удалим из кода всё, что не является частью торговой системы.
Популяционные алгоритмы оптимизации: Рой частиц (PSO) Популяционные алгоритмы оптимизации: Рой частиц (PSO)
В данной статье рассмотрим популярный алгоритм "Рой Частиц" (PSO — particle swarm optimisation). Ранее мы обсудили такие важные характеристики алгоритмов оптимизации как сходимость, скорость сходимости, устойчивость, масштабируемость, разработали стенд для тестирования, рассмотрели простейший алгоритм на ГСЧ.
Разработка торгового советника с нуля (Часть 26): Навстречу будущему (I) Разработка торгового советника с нуля (Часть 26): Навстречу будущему (I)
Сегодня мы выведем нашу систему ордеров на новый уровень, но сначала нам нужно решить несколько задач. Сейчас у нас есть разные вопросы, которые связаны с тем, как мы хотим работать и какие вещи мы делаем в течение торгового дня.
Простое создание сложных индикаторов с помощью объектов Простое создание сложных индикаторов с помощью объектов
В статье представлен метод создания сложных индикаторов, позволяющий избежать проблем при работе с несколькими графиками и буферами, а также при объединении данных из нескольких источников.