DoEasy. Элементы управления (Часть 31): Прокрутка содержимого элемента управления "ScrollBar"

Artyom Trishkin | 22 декабря, 2022

Содержание


Концепция

Продолжаем развивать функционал элемента управления ScrollBar. Разрабатываемая нами полоса прокрутки может реагировать на нажатие кнопок и смещение ползунка. Но вот далее никаких действий не происходит. А нам нужно, чтобы при нажатии на кнопки прокрутки, содержимое контейнера смещалось внутри него, тем самым показывая ранее скрытые области и скрывая ранее отображаемые с противоположной стороны контейнера. Сегодня создадим возможность прокрутки содержимого контейнера при нажатии на кнопки горизонтальной полосы прокрутки. При этом ползунок элемента управления будет автоматически подстраивать свои размер и положение.

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

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

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


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

Так как ползунок полосы прокрутки автоматически подстраивает свои размеры в зависимости от того, насколько содержимое контейнера выходит за его пределы, то при сильном уменьшении размера, ползунок может стать слишком маленьким. Во избежание такой ситуации необходимо задать минимальный размер ползунка, меньше которого он быть не может. А при перемещении содержимого контейнера нужно задать размер шага в пикселях, на которые содержимое контейнера может смещаться за один шаг. В редакторе MetaEditor, например, такой шаг равен шести пикселям. Мы сделаем поменьше — два пикселя.

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

#define DEF_CONTROL_SCROLL_BAR_WIDTH                  (11)                 // Ширина элемента управления ScrollBar по умолчанию
#define DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN         (8)                  // Минимальный размер области захвата (ползунка)
#define DEF_CONTROL_SCROLL_BAR_SCROLL_STEP            (2)                  // Шаг сдвига в пикселях содержимого контейнера при прокрутке
#define DEF_CONTROL_CORNER_AREA                       (4)                  // Количество пикселей, определяющих область угла для изменения размеров
#define DEF_CONTROL_LIST_MARGIN_X                     (1)                  // Зазор между столбцами в элементах управления ListBox
#define DEF_CONTROL_LIST_MARGIN_Y                     (0)                  // Зазор между строками в элементах управления ListBox


Когда мы хотим получить данные из свойств объекта "верхняя, нижняя, левая и правая границы графического элемента", то мы их получаем из свойств объекта. В эти параметры записываются значения при успешном создании объекта. А далее, если мы изменяем размеры объекта, то в свойства графического элемента эти данные уже не сохраняются. Да, мы получаем значения когда запрашиваем их одним из методов, возвращающих данное свойство, например, BottomEdge(), но этот метод просто возвращает рассчитанное значение. В свойствах же объекта эти значения не меняются. И это нужно исправить. В файле \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh базового графического элемента библиотеки, во всех методах, в которых как-то меняются границы объекта, нужно прописать запись новых значений в свойства объекта:

//+------------------------------------------------------------------+
//| Устанавливает новую координату X                                 |
//+------------------------------------------------------------------+
bool CGCnvElement::SetCoordX(const int coord_x)
  {
   int x=(int)::ObjectGetInteger(this.ChartID(),this.NameObj(),OBJPROP_XDISTANCE);
   if(coord_x==x)
     {
      if(coord_x==this.GetProperty(CANV_ELEMENT_PROP_COORD_X))
         return true;
      this.SetProperty(CANV_ELEMENT_PROP_COORD_X,coord_x);
      this.SetRightEdge();
      return true;
     }
   if(::ObjectSetInteger(this.ChartID(),this.NameObj(),OBJPROP_XDISTANCE,coord_x))
     {
      this.SetProperty(CANV_ELEMENT_PROP_COORD_X,coord_x);
      this.SetRightEdge();
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//| Устанавливает новую координату Y                                 |
//+------------------------------------------------------------------+
bool CGCnvElement::SetCoordY(const int coord_y)
  {
   int y=(int)::ObjectGetInteger(this.ChartID(),this.NameObj(),OBJPROP_YDISTANCE);
   if(coord_y==y)
     {
      if(coord_y==this.GetProperty(CANV_ELEMENT_PROP_COORD_Y))
         return true;
      this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,coord_y);
      this.SetBottomEdge();
      return true;
     }
   if(::ObjectSetInteger(this.ChartID(),this.NameObj(),OBJPROP_YDISTANCE,coord_y))
     {
      this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,coord_y);
      this.SetBottomEdge();
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//| Устанавливает новую ширину                                       |
//+------------------------------------------------------------------+
bool CGCnvElement::SetWidth(const int width)
  {
   if(this.GetProperty(CANV_ELEMENT_PROP_WIDTH)==width)
      return true;
   if(!this.m_canvas.Resize(width,this.m_canvas.Height()))
     {
      CMessage::ToLog(DFUN+this.TypeElementDescription()+": width="+(string)width+": ",MSG_CANV_ELEMENT_ERR_FAILED_SET_WIDTH);
      return false;
     }
   this.SetProperty(CANV_ELEMENT_PROP_WIDTH,width);
   this.SetVisibleAreaX(0,true);
   this.SetVisibleAreaWidth(width,true);
   this.SetRightEdge();
   return true;
  }
//+------------------------------------------------------------------+
//| Устанавливает новую высоту                                       |
//+------------------------------------------------------------------+
bool CGCnvElement::SetHeight(const int height)
  {
   if(this.GetProperty(CANV_ELEMENT_PROP_HEIGHT)==height)
      return true;
   if(!this.m_canvas.Resize(this.m_canvas.Width(),height))
     {
      CMessage::ToLog(DFUN+this.TypeElementDescription()+": height="+(string)height+": ",MSG_CANV_ELEMENT_ERR_FAILED_SET_HEIGHT);
      return false;
     }
   this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,height);
   this.SetVisibleAreaY(0,true);
   this.SetVisibleAreaHeight(height,true);
   this.SetBottomEdge();
   return true;
  }
//+------------------------------------------------------------------+

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


Некоторые методы, созданные нами ранее в классе объекта-контейнера, нужны только для объектов-контейнеров. Например, методы, возвращающие границы рабочей области контейнера, внутри которой могут быть расположены прикреплённые объекты. Но тут оказалось проблемой то, что в других объектах библиотеки, кроме объектов-контейнеров, данные методы не доступны. А они часто нужны — ведь мы обращаемся к объекту-контейнеру из класса, в котором нет доступа к такому объекту (он попросту о нём не знает). Соответственно, нам приходится обращаться к свойствам такого объекта через свойства его родителя — базового объекта всех WinForms-объектов библиотеки. А этот класс не видит методов своего наследника — класса объекта-контейнера. Получается замкнутый круг. Но выход есть: мы, немного теряя в структурированности объектов, перенесём все нужные методы в класс CWinFormBase, который является родительским для всех WinForms-объектов библиотеки. Таким образом, нам, при обращении к разным свойствам разных по назначению объектов друг из друга, будет проще получать нужные данные. Пусть даже, что такие данные относятся к иному типу объекта, и в данном объекте не используются, но зато все объекты смогут обращаться к методам других объектов, не используемых в текущем, но использующихся в тех, к которым идёт обращение.

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

Из файла \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh вырежем эти публичные методы:

public:
//--- Возвращает размеры и координаты рабочей области
   int               WidthWorkspace(void)          const
                       {
                        return this.Width()-::fmax(this.BorderSizeLeft(),this.PaddingLeft())-::fmax(this.BorderSizeRight(),this.PaddingRight());
                       }
   int               HeightWorkspace(void)         const
                       {
                        return this.Height()-::fmax(this.BorderSizeTop(),this.PaddingTop())-::fmax(this.BorderSizeBottom(),this.PaddingBottom());
                       }
   int               CoordXWorkspace(void)         const
                       {
                        return this.CoordX()+::fmax(this.BorderSizeLeft(),this.PaddingLeft());
                       }
   int               CoordYWorkspace(void)         const
                       {
                        return this.CoordY()+::fmax(this.BorderSizeTop(),this.PaddingTop());
                       }
   int               RightEdgeWorkspace(void)      const
                       {
                        return this.RightEdge()-::fmax(this.BorderSizeRight(),this.PaddingRight());
                       }
   int               BottomEdgeWorkspace(void)     const
                       {
                        return this.BottomEdge()-::fmax(this.BorderSizeBottom(),this.PaddingBottom());
                       }

//--- Возвращает список прикреплённых WinForms-объектов с (1) любым, (2) указанным типом WinForms-объекта от базового и выше

и вставим их тоже в публичную секцию класса в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh:

//--- Деструктор
                    ~CWinFormBase(void)
                      {
                       if(this.m_list_active_elements!=NULL)
                         {
                           this.m_list_active_elements.Clear();
                           delete this.m_list_active_elements;
                         }
                      }
                      
//--- Возвращает размеры и координаты рабочей области
   int               WidthWorkspace(void)       const { return this.Width()-::fmax(this.BorderSizeLeft(),this.PaddingLeft())-::fmax(this.BorderSizeRight(),this.PaddingRight()); }
   int               HeightWorkspace(void)      const { return this.Height()-::fmax(this.BorderSizeTop(),this.PaddingTop())-::fmax(this.BorderSizeBottom(),this.PaddingBottom());}
   int               CoordXWorkspace(void)      const { return this.CoordX()+::fmax(this.BorderSizeLeft(),this.PaddingLeft());                           }
   int               CoordYWorkspace(void)      const { return this.CoordY()+::fmax(this.BorderSizeTop(),this.PaddingTop());                             }
   int               RightEdgeWorkspace(void)   const { return this.RightEdge()-::fmax(this.BorderSizeRight(),this.PaddingRight());                      }
   int               BottomEdgeWorkspace(void)  const { return this.BottomEdge()-::fmax(this.BorderSizeBottom(),this.PaddingBottom());                   }
                      
//--- (1) Устанавливает, (2) возвращает цвет текста по умолчанию всех объектов на панели
   void              SetForeColor(const color clr,const bool set_init_color)
                       {
                        if(this.ForeColor()==clr)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR,clr);
                        if(set_init_color)
                           this.SetForeColorInit(clr);
                       }

Теперь эти методы будут видны из всех WinForms-объектов библиотеки.

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

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

В приватной секции класса объявим такую структуру и переменную с типом этой структуры, в которую будем записывать нужные нам данные:

protected:
   CArrayObj        *m_list_active_elements;                   // Указатель на список активных элементов
   color             m_fore_color_init;                        // Первоначальный цвет текста элемента
   color             m_fore_state_on_color_init;               // Первоначальный цвет текста элемента в состоянии "ON"
private:
   struct SOversizes                                           // Структура значений выхода привязанных объектов за пределы контейнера
     {
      int   top;     // сверху
      int   bottom;  // снизу
      int   left;    // слева
      int   right;   // справа
     };
   SOversizes        m_oversize;                               // Структура значений выхода за пределы контейнера
//--- Возвращает флаги шрифта
   uint              GetFontFlags(void);

public:


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

//--- Перерисовывает объект
   virtual void      Redraw(bool redraw);
//--- Устанавливает новые размеры (1) текущему, (2) указанному по индексу объекту
   virtual bool      Resize(const int w,const int h,const bool redraw);
   virtual bool      Resize(const int index,const int w,const int h,const bool redraw);
//--- Возвращает флаг выхода содержимого контейнера за его пределы
   bool              CheckForOversize(void);
//--- Смещает все привязанные объекты
   bool              ShiftDependentObj(const int shift_x,const int shift_y);
//--- Возвращает (1) максимальное, (2) минимальное значение указанного целочисленного свойства из всех прикреплённых объектов к текущему
   long              GetMaxLongPropFromDependent(const ENUM_CANV_ELEMENT_PROP_INTEGER prop);
   long              GetMinLongPropFromDependent(const ENUM_CANV_ELEMENT_PROP_INTEGER prop);
protected:
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CWinFormBase(const ENUM_GRAPH_ELEMENT_TYPE type,
                                  CGCnvElement *main_obj,CGCnvElement *base_obj,
                                  const long chart_id,
                                  const int subwindow,
                                  const string descript,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
public:
//--- Конструктор

Все эти методы нужны для работы с объектами-контейнерами, но вызываться будут далее из объектов не контейнеров, поэтому они расположены в общем родительском классе всех WinForms-объектов.

Так как структура и переменная с типом структуры приватная, то нам нужны пуличные методы для возврата значений, записанных в поля этой структуры. Напишем их в публичной секции в самом конце тела класса:

//--- Возвращает количество пикселей, на которые прикреплённые объекты выходят за пределы контейнера (1) сверху, (2) снизу, (3) слева, (4) справа
   int               OversizeTop(void)                         const { return this.m_oversize.top;    }
   int               OversizeBottom(void)                      const { return this.m_oversize.bottom; }
   int               OversizeLeft(void)                        const { return this.m_oversize.left;   }
   int               OversizeRight(void)                       const { return this.m_oversize.right;  }
  };
//+------------------------------------------------------------------+


В обоих конструкторах класса инициализируем все поля структуры нулями:

//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CWinFormBase::CWinFormBase(const ENUM_GRAPH_ELEMENT_TYPE type,
                           CGCnvElement *main_obj,CGCnvElement *base_obj,
                           const long chart_id,
                           const int subwindow,
                           const string descript,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CForm(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Установим объекту указанный тип графического элемента, а тип объекта библиотеки - как тип этого объекта
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
//--- Инициализируем все переменные
   this.SetText("");
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetForeStateOnColor(this.ForeColor(),true);
   this.SetForeStateOnColorMouseDown(this.ForeColor());
   this.SetForeStateOnColorMouseOver(this.ForeColor());
   this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY);
   this.SetFontBoldType(FW_TYPE_NORMAL);
   this.SetMarginAll(0);
   this.SetPaddingAll(0);
   this.SetBorderSizeAll(0);
   this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetAutoSize(false,false);
   CForm::SetCoordXInit(x);
   CForm::SetCoordYInit(y);
   CForm::SetWidthInit(w);
   CForm::SetHeightInit(h);
   this.m_shadow=false;
   this.m_gradient_v=true;
   this.m_gradient_c=false;
   this.m_list_active_elements=new CArrayObj();
   ::ZeroMemory(this.m_oversize);
  }
//+------------------------------------------------------------------+
//| Конструктор с указанием главного и базового объектов,            |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CWinFormBase::CWinFormBase(CGCnvElement *main_obj,CGCnvElement *base_obj,
                           const long chart_id,
                           const int subwindow,
                           const string descript,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CForm(GRAPH_ELEMENT_TYPE_WF_BASE,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Установим тип графического элемента и тип объекта библиотеки как базовый WinForms-объект
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE);
   this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
//--- Инициализируем все переменные
   this.SetText("");
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetForeStateOnColor(this.ForeColor(),true);
   this.SetForeStateOnColorMouseDown(this.ForeColor());
   this.SetForeStateOnColorMouseOver(this.ForeColor());
   this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY);
   this.SetFontBoldType(FW_TYPE_NORMAL);
   this.SetMarginAll(0);
   this.SetPaddingAll(0);
   this.SetBorderSizeAll(0);
   this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetAutoSize(false,false);
   CForm::SetCoordXInit(x);
   CForm::SetCoordYInit(y);
   CForm::SetWidthInit(w);
   CForm::SetHeightInit(h);
   this.m_shadow=false;
   this.m_gradient_v=true;
   this.m_gradient_c=false;
   this.m_list_active_elements=new CArrayObj();
   ::ZeroMemory(this.m_oversize);
  }
//+------------------------------------------------------------------+


Метод, возвращающий флаг выхода содержимого контейнера за его пределы:

//+------------------------------------------------------------------+
//| Возвращает флаг выхода содержимого контейнера за его пределы     |
//+------------------------------------------------------------------+
bool CWinFormBase::CheckForOversize(void)
  {
//--- Обнуляем структуру значений выхода привязанных объектов за пределы контейнера
   ::ZeroMemory(this.m_oversize);
//--- В цикле по количеству привязанных объектов
   for(int i=0;i<this.ElementsTotal();i++)
     {
      //--- Получаем очередной объект и пропускаем полосы прокрутки
      CWinFormBase *obj=this.GetElement(i);
      if(obj==NULL                                                            || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR             || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL  || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL)
         continue;
      //--- Получаем значение в пикселях выхода объекта справа за пределы формы
      //--- Если значение больше нуля и больше записанного в поле структуры - сохраняем его в структуре
      int r=obj.RightEdge()-this.RightEdgeWorkspace();
      if(r>0 && r>this.m_oversize.right)
         this.m_oversize.right=r;
      //--- Получаем значение в пикселях выхода объекта слева за пределы формы
      //--- Если значение больше нуля и больше записанного в поле структуры - сохраняем его в структуре
      int l=this.CoordXWorkspace()-obj.CoordX();
      if(l>0 && l>this.m_oversize.left)
         this.m_oversize.left=l;
      //--- Получаем значение в пикселях выхода объекта сверху за пределы формы
      //--- Если значение больше нуля и больше записанного в поле структуры - сохраняем его в структуре
      int t=this.CoordYWorkspace()-obj.CoordY();
      if(t>0 && t>this.m_oversize.top)
         this.m_oversize.top=t;
      //--- Получаем значение в пикселях выхода объекта снизу за пределы формы
      //--- Если значение больше нуля и больше записанного в поле структуры - сохраняем его в структуре
      int b=obj.BottomEdge()-this.BottomEdgeWorkspace();
      if(b>0 && b>this.m_oversize.bottom)
         this.m_oversize.bottom=b;
     }
//--- Возвращаем флаг того, что хотя бы с одной стороны привязанный объект выходит за границы формы
   return(m_oversize.top>0 || m_oversize.bottom>0 || m_oversize.left>0 || m_oversize.right>0);
  }
//+------------------------------------------------------------------+

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

Метод, смещающий все привязанные объекты:

//+------------------------------------------------------------------+
//| Смещает все привязанные объекты                                  |
//+------------------------------------------------------------------+
bool CWinFormBase::ShiftDependentObj(const int shift_x,const int shift_y)
  {
//--- В цикле по всем привязанным объектам
   for(int i=0;i<this.ElementsTotal();i++)
     {
      //--- получаем очередной объект и пропускаем полосы прокрутки
      CWinFormBase *obj=this.GetElement(i);
      if(obj==NULL                                                            || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR             || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL  || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL
        ) continue;
      //--- Устанавливаем координаты для смещения
      int x=obj.CoordX()+shift_x;
      int y=obj.CoordY()+shift_y;
      if(!obj.Move(x,y,false))
         return false;
      //--- После успешного смещения устанавливаем относительные координаты и перерисовываем объект
      obj.SetCoordXRelative(obj.CoordX()-this.CoordX());
      obj.SetCoordYRelative(obj.CoordY()-this.CoordY());
      obj.Redraw(false);
     }
   return true;
  }
//+------------------------------------------------------------------+

Логика метода прокомментирована в коде. Нам нужно сместить привязанные к контейнеру объекты при щелчке по кнопкам прокрутки или при перемещении ползунка. Этот метод смещает все объекты, находящиеся в списке привязанных к нему объектов. Смещение производится методом Move(), а в этом методе уже всё сделано для того, чтобы и привязанные к перемещаемому объекту другие элементы, тоже перемещались. В общем — здесь мы смещаем на указанную величину все привязанные к контейнеру объекты кроме полос прокрутки, так как они являются управляющими элементами объекта-контейнера, а не привязанными к нему объектам (хоть и находятся в общем списке).


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

//+------------------------------------------------------------------+
//| Возвращает максимальное значение указанного целочисленного       |
//| свойства из всех подчинённых базовому объектов                   |
//+------------------------------------------------------------------+
long CWinFormBase::GetMaxLongPropFromDependent(const ENUM_CANV_ELEMENT_PROP_INTEGER prop)
  {
//--- Инициализируем property значением -1
   long property=-LONG_MAX;
//--- В цикле по списку привязанных объектов
   for(int i=0;i<this.ElementsTotal();i++)
     {
      //--- получаем очередной объект и пропускаем полосы прокрутки
      CWinFormBase *obj=this.GetElement(i);
      if(obj==NULL                                                            || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR             || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL  || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL
        ) continue;
      //--- Если значение свойства полученного объекта больше значения, записанного в property -
      //--- устанавливаем в property значение свойства текущего объекта
      if(obj.GetProperty(prop)>property)
         property=obj.GetProperty(prop);
      //--- Получаем максимальное значение свойства из привязанных объектов к текущему
      long prop_form=obj.GetMaxLongPropFromDependent(prop);
      //--- Если полученное значение больше значения в property
      //--- устанавливаем в property полученное значение
      if(prop_form>property)
         property=prop_form;
     }
//--- Возвращаем найденное максимальное значение свойства
   return property;
  }
//+------------------------------------------------------------------+

Здесь тоже вся логика прописана в комментариях к коду, и тут всё просто: в цикле ищем среди всех объектов (кроме полос прокрутки) объект с максимальным значением указанного свойства. Найденное максимальное значение и возвращаем.


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

//+------------------------------------------------------------------+
//| Возвращает минимальное значение указанного целочисленного        |
//| свойства из всех подчинённых базовому объектов                   |
//+------------------------------------------------------------------+
long CWinFormBase::GetMinLongPropFromDependent(const ENUM_CANV_ELEMENT_PROP_INTEGER prop)
  {
//--- Инициализируем property значением LONG_MAX
   long property=LONG_MAX;
//--- В цикле по списку привязанных объектов
   for(int i=0;i<this.ElementsTotal();i++)
     {
      //--- получаем очередной объект и пропускаем полосы прокрутки
      CWinFormBase *obj=this.GetElement(i);
      if(obj==NULL                                                            || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR             || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL  || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL
        ) continue;
      //--- Если значение свойства полученного объекта меньше значения, записанного в property -
      //--- устанавливаем в property значение свойства текущего объекта
      if(obj.GetProperty(prop)<property)
         property=obj.GetProperty(prop);
      //--- Получаем минимальное значение свойства из привязанных объектов к текущему
      long prop_form=obj.GetMinLongPropFromDependent(prop);
      //--- Если полученное значение меньше значения в property
      //--- устанавливаем в property полученное значение
      if(prop_form<property)
         property=prop_form;
     }
//--- Возвращаем найденное минимальное значение свойства
   return property;
  }
//+------------------------------------------------------------------+

Метод аналогичен предыдущему: в цикле ищем среди всех объектов (кроме полос прокрутки) объект с минимальным значением указанного свойства. Найденное минимальное значение возвращаем.


При наведении курсора мышки на ползунок полосы прокрутки, весь объект ScrollBar нужно переместить на передний план. Да и не только при наведении на ползунок, а вообще, всегда при наведении мышки на область этого объекта, его нужно переместить выше всех объектов контейнера. Так нужно для того, чтобы курсор мог взаимодействовать именно с самым верхним объектом, а не с теми, что могут находиться выше него, так как созданы были позднее. На данный момент класс объекта-области захвата унаследован от класса объекта-кнопки и использует функционал родительского класса для взаимодействия с мышкой. Так как методы для обработки разных событий при взаимодействии с мышкой виртуальные, то нам нужно их переопределить в классе объекта-области захвата.

В файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarThumb.mqh, в защищённой секции объявим виртуальные обработчики событий мышки, а в публичной — обработчик последнего события мышки:

//+------------------------------------------------------------------+
//| Класс объекта Label элементов управления WForms                  |
//+------------------------------------------------------------------+
class CScrollBarThumb : public CButton
  {
private:

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

//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CScrollBarThumb(const ENUM_GRAPH_ELEMENT_TYPE type,
                                     CGCnvElement *main_obj,CGCnvElement *base_obj,
                                     const long chart_id,
                                     const int subwindow,
                                     const string descript,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h);
                             
public:
//--- Обработчик последнего события мышки
   virtual void      OnMouseEventPostProcessing(void);

//--- Конструктор
                     CScrollBarThumb(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                     const long chart_id,
                                     const int subwindow,
                                     const string descript,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h);
  };
//+------------------------------------------------------------------+


Напишем реализацию объявленных виртуальных обработчиков.

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

//+------------------------------------------------------------------+
//| Обработчик события Курсор в пределах активной области,           |
//| кнопки мышки не нажаты                                           |
//+------------------------------------------------------------------+
void CScrollBarThumb::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   CWinFormBase *base=this.GetBase();
   if(base!=NULL)
     {
      base.BringToTop();
     }
   CButton::MouseActiveAreaNotPressedHandler(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

Здесь: получаем указатель на базовый объект, а это будет объект-полоса прокрутки и переносим его на передний план. Далее вызываем обработчик соответствующего методу события мышки родительского объекта.


Обработчик события "Курсор в пределах активной области, нажата кнопка мышки (любая)":

//+------------------------------------------------------------------+
//| Обработчик события Курсор в пределах активной области,           |
//| нажата кнопка мышки (любая)                                      |
//+------------------------------------------------------------------+
void CScrollBarThumb::MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   CWinFormBase *base=this.GetBase();
   if(base!=NULL)
     {
      base.BringToTop();
     }
   CButton::MouseActiveAreaPressedHandler(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

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


Обработчик события "Курсор в пределах активной области, отжата кнопка мышки (левая)":

//+------------------------------------------------------------------+
//| Обработчик события Курсор в пределах активной области,           |
//| отжата кнопка мышки (левая)                                      |
//+------------------------------------------------------------------+
void CScrollBarThumb::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   CWinFormBase *base=this.GetBase();
   if(base!=NULL)
     {
      base.BringToTop();
     }
   CButton::MouseActiveAreaReleasedHandler(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

Идентично двум вышерассмотренным обработчикам.


Обработчик последнего события мышки просто перенесён полностью из родительского объекта с целью вероятной его дальнейшей доработки:

//+------------------------------------------------------------------+
//| Обработчик последнего события мышки                              |
//+------------------------------------------------------------------+
void CScrollBarThumb::OnMouseEventPostProcessing(void)
  {
   if(!this.IsVisible() || !this.Enabled())
      return;
   ENUM_MOUSE_FORM_STATE state=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       :
        if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED)
          {
           this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColor() : this.BackgroundColorInit(),false);
           this.SetForeColor(this.State() ? this.ForeStateOnColor() : this.ForeColorInit(),false);
           this.SetBorderColor(this.BorderColorInit(),false);
           this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT);
           this.Redraw(false);
          }
        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_BOTTOM_NOT_PRESSED       :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_BOTTOM_PRESSED           :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_BOTTOM_WHEEL             :
//--- В пределаз области прокрутки справа
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_RIGHT_NOT_PRESSED        :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_RIGHT_PRESSED            :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_RIGHT_WHEEL              :
//--- В пределах области изменения размеров окна сверху
      case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_AREA_NOT_PRESSED          :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_AREA_PRESSED              :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_AREA_WHEEL                :
//--- В пределах области изменения размеров окна снизу
      case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_AREA_NOT_PRESSED       :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_AREA_PRESSED           :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_AREA_WHEEL             :
//--- В пределах области изменения размеров окна слева
      case MOUSE_FORM_STATE_INSIDE_RESIZE_LEFT_AREA_NOT_PRESSED         :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_LEFT_AREA_PRESSED             :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_LEFT_AREA_WHEEL               :
//--- В пределах области изменения размеров окна справа
      case MOUSE_FORM_STATE_INSIDE_RESIZE_RIGHT_AREA_NOT_PRESSED        :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_RIGHT_AREA_PRESSED            :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_RIGHT_AREA_WHEEL              :
//--- В пределах области изменения размеров окна сверху-слева
      case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_LEFT_AREA_NOT_PRESSED     :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_LEFT_AREA_PRESSED         :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_LEFT_AREA_WHEEL           :
//--- В пределах области изменения размеров окна сверху-справа
      case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_RIGHT_AREA_NOT_PRESSED    :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_RIGHT_AREA_PRESSED        :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_RIGHT_AREA_WHEEL          :
//--- В пределах области изменения размеров окна снизу-слева
      case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_LEFT_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_LEFT_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_LEFT_AREA_WHEEL        :
//--- В пределах области изменения размеров окна снизу-справа
      case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_RIGHT_AREA_NOT_PRESSED :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_RIGHT_AREA_PRESSED     :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_RIGHT_AREA_WHEEL       :
//--- В пределах области управления
      case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_NOT_PRESSED             :
      case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED                 :
      case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL                   :
        break;
      //---MOUSE_EVENT_NO_EVENT
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

На данный момент обработчик идентичен обработчику родительского класса.


В классе абстрактного объекта-полосы прокрутки в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBar.mqh в методе, создающем объект-область захвата (ползунок) и кнопки прокрутки, укажем правильные размеры кнопок, записанные в макроподстановке, специально предназначенной для хранения размеров кнопок полосы прокрутки:

//+------------------------------------------------------------------+
//| Создаёт объект-область захвата                                   |
//+------------------------------------------------------------------+
void CScrollBar::CreateThumbArea(void)
  {
   this.CreateArrowButtons(DEF_CONTROL_SCROLL_BAR_WIDTH,DEF_CONTROL_SCROLL_BAR_WIDTH);
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Рассчитывает размер области захвата                              |
//+------------------------------------------------------------------+
int CScrollBar::CalculateThumbAreaSize(void)
  {
   return DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN;
  }
//+------------------------------------------------------------------+


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

Сегодня создадим такой функционал для горизонтальной полосы прокрутки. Далее, после завершения его разработки, перенесём готовый функционал в класс объекта вертикальной полосы прокрутки. А далее сделаем их совместную работу.

В файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarHorisontal.mqh переименуем приватный метод CalculateThumbAreaSize в метод CalculateThumbAreaDistance(). В публичной секции объявим методы для рассчёта размеров и координат ползунка и основной метод перерасчёта его параметров:

//+------------------------------------------------------------------+
//| Класс объекта CScrollBarHorisontal элементов управления WForms     |
//+------------------------------------------------------------------+
class CScrollBarHorisontal : public CScrollBar
  {
private:
//--- Создаёт объекты ArrowButton
   virtual void      CreateArrowButtons(const int width,const int height);
//--- Рассчитывает дистанцию области захвата (ползунка)
   int               CalculateThumbAreaDistance(const int thumb_size);

protected:
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CScrollBarHorisontal(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          CGCnvElement *main_obj,CGCnvElement *base_obj,
                                          const long chart_id,
                                          const int subwindow,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h);

public:
//--- Поддерживаемые свойства объекта (1) целочисленные, (2) вещественные, (3) строковые
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property)  { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)  { return true; }
   
//--- Возвращает кнопку со стрелкой (1) влево, (2) вправо
   CArrowLeftButton *GetArrowButtonLeft(void)   { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,0);    }
   CArrowRightButton*GetArrowButtonRight(void)  { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,0);   }

//--- Возвращает размер рабочей области ползунка
   int               BarWorkAreaSize(void);
//--- Возвращает координату начала рабочей области ползунка
   int               BarWorkAreaCoord(void);
   
//--- Устанавливает новые размеры
   virtual bool      Resize(const int w,const int h,const bool redraw);
//--- Рассчитывает и устанавливает параметры области захвата (ползунка)
   int               SetThumbParams(void);

//--- Конструктор
                     CScrollBarHorisontal(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                          const long chart_id,
                                          const int subwindow,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h);
//--- Таймер
   virtual void      OnTimer(void);
//--- Обработчик событий
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
  };
//+------------------------------------------------------------------+


Рассмотрим объявленные методы.

Метод, устанавливающий новые размеры объекту:

//+------------------------------------------------------------------+
//| Устанавливает новые размеры                                      |
//+------------------------------------------------------------------+
bool CScrollBarHorisontal::Resize(const int w,const int h,const bool redraw)
  {
//--- Если размеры объекта изменить не удалось - возвращаем false
   if(!CWinFormBase::Resize(w,h,redraw))
      return false;
//--- Получаем объект-кнопку со стрелкой вправо
   CArrowRightButton *br=this.GetArrowButtonRight();
//--- Если кнопка не получена - возвращаем false
   if(br==NULL)
      return false;
//--- Перемещаем кнопку к правому краю полосы прокрутки
   if(br.Move(this.RightEdge()-this.BorderSizeRight()-br.Width(),br.CoordY()))
     {
      //--- Устанавливаем кнопке новые относительные координаты
      br.SetCoordXRelative(br.CoordX()-this.CoordX());
      br.SetCoordYRelative(br.CoordY()-this.CoordY());
     }
//--- Устанавливаем параметры ползунка
   this.SetThumbParams();
//--- Успешно
   return true;
  }
//+------------------------------------------------------------------+

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


Метод, рассчитывающий и устанавливающий параметры области захвата (ползунка):

//+------------------------------------------------------------------+
//| Рассчитывает и устанавливает параметры области захвата (ползунка)|
//+------------------------------------------------------------------+
int CScrollBarHorisontal::SetThumbParams(void)
  {
//--- Получаем базовый объект
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return 0;
//--- Получаем объект-область захвата (ползунок)
   CScrollBarThumb *thumb=this.GetThumb();
   if(thumb==NULL)
      return 0;
//--- Получаем размер видимой части по ширине внутри контейнера
   int base_w=base.WidthWorkspace();
//--- Рассчитываем полную ширину всех привязанных объектов и размер окна видимой части в %
   int objs_w=base_w+base.OversizeLeft()+base.OversizeRight();
   double px=base_w*100.0/objs_w;
//--- Рассчитываем и корректируем размер ползунка в % относительно ширины его рабочей области (не меньше минимального размера)
   int thumb_size=(int)::ceil(this.BarWorkAreaSize()/100.0*px);
   if(thumb_size<DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN)
      thumb_size=DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN;
//--- Рассчитываем координату ползунка и изменяем его размер под ранее рассчитанный
   int thumb_x=this.CalculateThumbAreaDistance(thumb_size);
   if(!thumb.Resize(thumb_size,thumb.Height(),true))
      return 0;
//--- Смещаем ползунок на рассчитанную координату X
   if(thumb.Move(this.BarWorkAreaCoord()+thumb_x,thumb.CoordY()))
     {
      thumb.SetCoordXRelative(thumb.CoordX()-this.CoordX());
      thumb.SetCoordYRelative(thumb.CoordY()-this.CoordY());
     }
//--- Возвращаем рассчитанный размер ползунка
   return thumb_size;
  }
//+------------------------------------------------------------------+

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


Метод, рассчитывающий дистанцию области захвата (ползунка):

//+------------------------------------------------------------------+
//| Рассчитывает дистанцию области захвата (ползунка)                |
//+------------------------------------------------------------------+
int CScrollBarHorisontal::CalculateThumbAreaDistance(const int thumb_size)
  {
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return 0;
   double x=(double)thumb_size/(double)base.WidthWorkspace();
   return (int)::ceil((double)base.OversizeLeft()*x);
  }
//+------------------------------------------------------------------+

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


Метод, возвращаюший размер рабочей области ползунка:

//+------------------------------------------------------------------+
//| Возвращает размер рабочей области ползунка                       |
//+------------------------------------------------------------------+
int CScrollBarHorisontal::BarWorkAreaSize(void)
  {
   CArrowLeftButton  *bl=this.GetArrowButtonLeft();
   CArrowRightButton *br=this.GetArrowButtonRight();
   int x1=(bl!=NULL ? bl.RightEdge() : this.CoordX()+this.BorderSizeLeft());
   int x2=(br!=NULL ? br.CoordX() : this.RightEdge()-this.BorderSizeRight());
   return(x2-x1);
  }
//+------------------------------------------------------------------+

Рабочая область ползунка — это та область, внутри которой он перемещается в полосе прокрутке. Иными словами — это область между двумя кнопками со стрелками полосы прокрутки. Поэтому для рассёта этой дистанции нам нужно получить указатели на эти кнопки и рассчитать дистанцию между левым краем правой кнопки и правым краем левой.


Метод, возвращаюший координату начала рабочей области ползунка:

//+------------------------------------------------------------------+
//| Возвращает координату начала рабочей области ползунка            |
//+------------------------------------------------------------------+
int CScrollBarHorisontal::BarWorkAreaCoord(void)
  {
   CArrowLeftButton  *bl=this.GetArrowButtonLeft();
   return(bl!=NULL ? bl.RightEdge() : this.CoordX()+this.BorderSizeLeft());
  }
//+------------------------------------------------------------------+

Метод возвращает координату правого края левой кнопки полосы прокрутки. Именно эта координата является начальной для расположения ползунка, и именно от этой координаты мы и отсчитываем его положение при рассчёте его размеров и координат.


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

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CScrollBarHorisontal::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Корректируем смещение по Y для подокна
   CGCnvElement::OnChartEvent(id,lparam,dparam,sparam);
//--- Получаем указатели на управляющие объекты полосы прокрутки
   CArrowLeftButton  *buttl=this.GetArrowButtonLeft();
   CArrowRightButton *buttr=this.GetArrowButtonRight();
   CScrollBarThumb   *thumb=this.GetThumb();
   if(buttl==NULL || buttr==NULL || thumb==NULL)
      return;
//--- Если идентификатор события - перемещение объекта
   if(id==WF_CONTROL_EVENT_MOVING)
     {
      //--- Переносим полосу прокрутки на передний план
      this.BringToTop();
      //--- Объявляем переменные для координат области захвата
      int x=(int)lparam;
      int y=(int)dparam;
      //--- Устанавливаем координату Y равной координате Y элемента управления
      y=this.CoordY()+this.BorderSizeTop();
      //--- Корректируем координату X так, чтобы область захвата не выходила за пределы элемента управления с учётом кнопок со стрелками
      if(x<buttl.RightEdge())
        x=buttl.RightEdge();
      if(x>buttr.CoordX()-thumb.Width())
        x=buttr.CoordX()-thumb.Width();
      //--- Если объект-область захвата смещён на рассчитанные координаты
      if(thumb.Move(x,y,true))
        {
         //--- устанавливаем объекту его относительные координаты
         thumb.SetCoordXRelative(thumb.CoordX()-this.CoordX());
         thumb.SetCoordYRelative(thumb.CoordY()-this.CoordY());
         ::ChartRedraw(this.ChartID());
        }
      //--- Получаем указатель на базовый объект
      CWinFormBase *base=this.GetBase();
      if(base!=NULL)
        {
         //--- Проверяем выход содержимого за пределы контейнера и пересчитываем параметры ползунка
         base.CheckForOversize();
         this.SetThumbParams();
        }
     }
//--- Если щелчок по любой кнопке прокрутки
   if(id==WF_CONTROL_EVENT_CLICK_SCROLL_LEFT || id==WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT)
     {
      //--- Переносим полосу прокрутки на передний план
      this.BringToTop();
      //--- Получаем базовый объект
      CWinFormBase *base=this.GetBase();
      if(base==NULL)
         return;
      //--- Рассчитываем на сколько каждая сторона содержимого базового объекта выходит за его пределы
      base.CheckForOversize();
      //--- Получаем наибольшую и наименьшую координаты правой и левой сторон содержимого базового объекта
      int cntt_r=(int)base.GetMaxLongPropFromDependent(CANV_ELEMENT_PROP_RIGHT);
      int cntt_l=(int)base.GetMinLongPropFromDependent(CANV_ELEMENT_PROP_COORD_X);
      //--- Задаём количество пикселей, на которые нужно сместить содержимое базового объекта
      int shift=DEF_CONTROL_SCROLL_BAR_SCROLL_STEP;
      //--- Если щелчок по кнопке влево
      if(id==WF_CONTROL_EVENT_CLICK_SCROLL_LEFT)
        {
         if(cntt_l+shift<=base.CoordXWorkspace())
            base.ShiftDependentObj(shift,0);
        }
      //--- Если щелчок по кнопке вправо
      if(id==WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT)
        {
         if(cntt_r-shift>=base.RightEdgeWorkspace())
            base.ShiftDependentObj(-shift,0);
        }
      //--- Рассчитываем ширину и координаты ползунка
      this.SetThumbParams();
     }
  }
//+------------------------------------------------------------------+

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


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


Вернёмся к классу объекта-контейнера в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh.

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

//+------------------------------------------------------------------+
//| Класс базового объекта-контейнера элементов управления WForms    |
//+------------------------------------------------------------------+
class CContainer : public CWinFormBase
  {
private:
//--- Создаёт новый графический объект
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);

//--- Рассчитывает координаты привязки Dock-объектов
   void              CalculateCoords(CArrayObj *list);

//--- Создаёт (1) вертикальный, (2) горизонтальный ScrollBar
   CWinFormBase     *CreateScrollBarVertical(const int width);
   CWinFormBase     *CreateScrollBarHorisontal(const int width);

protected:
//--- Подстраивает размеры элемента под его внутреннее содержимое
   bool              AutoSizeProcess(const bool redraw);
//--- Устанавливает параметры присоединённому объекту
   void              SetObjParams(CWinFormBase *obj,const color colour);
//--- Создаёт объекты ScrollBar вертикальный и горизонтальный
   void              CreateScrollBars(const int width);
//--- Переносит полосы прокрутки на передний план
   void              BringToTopScrollBars(void);

public:
//--- Возвращает список прикреплённых WinForms-объектов с (1) любым, (2) указанным типом WinForms-объекта от базового и выше
   CArrayObj        *GetListWinFormsObj(void);
   CArrayObj        *GetListWinFormsObjByType(const ENUM_GRAPH_ELEMENT_TYPE type);
//--- Возвращает указатель на прикреплённый WinForms-объект с указанным типом по индексу
   CWinFormBase     *GetWinFormsObj(const ENUM_GRAPH_ELEMENT_TYPE type,const int index);
   
//--- Возвращает указатель на (1) вертикальную, (2) горизонтальную полосу прокрутки
   CScrollBarVertical   *GetScrollBarVertical(void)   { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL,0);  }
   CScrollBarHorisontal *GetScrollBarHorisontal(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL,0);}
   
//--- Устанавливает координату (1) X, (2) Y, (3) ширину, (4) высоту элемента
   virtual bool      SetCoordX(const int coord_x)              { return CGCnvElement::SetCoordX(coord_x);   }
   virtual bool      SetCoordY(const int coord_y)              { return CGCnvElement::SetCoordY(coord_y);   }
   virtual bool      SetWidth(const int width)                 { return CGCnvElement::SetWidth(width);      }
   virtual bool      SetHeight(const int height)               { return CGCnvElement::SetHeight(height);    }
   
//--- Устанавливает новые размеры (1) текущему, (2) указанному по индексу объекту
   virtual bool      Resize(const int w,const int h,const bool redraw);
//--- Обновляет координаты
   virtual bool      Move(const int x,const int y,const bool redraw=false);

//--- Создаёт новый присоединённый элемент
   virtual bool      CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool activity,
                                      const bool redraw);

//--- Перерисовывает объект
   virtual void      Redraw(bool redraw);
   
//--- Сбрасывает размеры всех привязанных объектов к изначальным
   bool              ResetSizeAllToInit(void);
//--- Располагает привязанные объекты в порядке их Dock-привязки
   virtual bool      ArrangeObjects(const bool redraw);


В методе, устанавливающем режим автоматического изменения размера элемента под содержимое, добавим проверку на то, что флаг автоматического изменения размеров под содержимое не установлен:

//--- (1) Устанавливает, (2) возвращает режим автоматического изменения размера элемента под содержимое
   void              SetAutoSizeMode(const ENUM_CANV_ELEMENT_AUTO_SIZE_MODE mode,const bool redraw)
                       {
                        ENUM_CANV_ELEMENT_AUTO_SIZE_MODE prev=this.AutoSizeMode();
                        if(prev==mode)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE,mode);
                        if(!this.AutoSize())
                           return;
                        if(prev!=this.AutoSizeMode() && this.ElementsTotal()>0)
                           this.AutoSizeProcess(redraw);
                       }
   ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void)   const { return (ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)this.GetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE); }

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


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

//+------------------------------------------------------------------+
//| Создаёт новый присоединённый элемент                             |
//+------------------------------------------------------------------+
bool CContainer::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h,
                                  const color colour,
                                  const uchar opacity,
                                  const bool activity,
                                  const bool redraw)
  {
//--- Если тип объекта - меньше, чем базовый WinForms-объект
   if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE)
     {
      //--- сообщаем об ошибке и возвращаем false
      CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE);
      return false;
     }
//--- Если не удалось создать новый графический элемент - возвращаем false
   CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity);
   if(obj==NULL)
      return false;
//--- Устанавливаем параметры созданному объекту
   this.SetObjParams(obj,colour);
//--- Если есть привязанные объекты
   if(this.ElementsTotal()>0)
     {
      //--- Если у панели включено автоизменение размеров - вызываем метод изменения размеров
      if(this.AutoSize())
         this.AutoSizeProcess(redraw);
      //--- При выключенном автоизменении размеров, определяем необходимость отображения полос прокрутки 
      else
        {
         this.CheckForOversize();
         //--- Если прикреплённые объекты выходят за окно видимости слева или справа
         if(this.OversizeLeft()>0 || this.OversizeRight()>0)
           {
            CScrollBarHorisontal *sbh=this.GetScrollBarHorisontal();
            if(sbh!=NULL)
              {
               sbh.SetThumbParams();
               sbh.SetDisplayed(true);
               sbh.Show();
              }
           }
        }
     }
//--- Обрезаем созданный объект по краям видимой части контейнера
   obj.Crop();
//--- возвращаем true
   return true;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Устанавливает новые размеры текущему объекту                     |
//+------------------------------------------------------------------+
bool CContainer::Resize(const int w,const int h,const bool redraw)
  {
//--- Если не удалось изменить размер контейнера - возвращаем false
   if(!CWinFormBase::Resize(w,h,redraw))
      return false;

//--- Получаем вертикальную полосу прокрутки и, если есть,
   CScrollBarVertical *scroll_v=this.GetScrollBarVertical();
   if(scroll_v!=NULL)
     {
      //--- Если вертикальный размер полосы прокрутки изменён под размер рабочей области контейнера
      if(scroll_v.Resize(scroll_v.Width(),this.HeightWorkspace(),false))
        {
         //--- Перемещаем вертикальную полосу прокрутки на новые координаты
         if(scroll_v.Move(this.RightEdgeWorkspace()-scroll_v.Width(),this.CoordYWorkspace()))
           {
            scroll_v.SetCoordXRelative(scroll_v.CoordX()-this.CoordX());
            scroll_v.SetCoordYRelative(scroll_v.CoordY()-this.CoordY());
           }
        }
      scroll_v.BringToTop();
     }
//--- Получаем горизонтальную полосу прокрутки и, если есть
   CScrollBarHorisontal *scroll_h=this.GetScrollBarHorisontal();//this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL,0);
   if(scroll_h!=NULL)
     {
      //--- Если горизонтальный размер полосы прокрутки изменён под размер рабочей области контейнера
      if(scroll_h.Resize(this.WidthWorkspace(),scroll_h.Height(),false))
        {
         //--- Перемещаем горизонтальную полосу прокрутки на новые координаты
         if(scroll_h.Move(this.CoordXWorkspace(),this.BottomEdgeWorkspace()-scroll_h.Height()))
           {
            scroll_h.SetCoordXRelative(scroll_h.CoordX()-this.CoordX());
            scroll_h.SetCoordYRelative(scroll_h.CoordY()-this.CoordY());
           }
        }
      scroll_h.BringToTop();
     }
   return true;
  }
//+------------------------------------------------------------------+

В связи с тем, что теперь не нужно обрабатывать здесь изменение размеров полос прокрутки для перемещения их кнопок на новые места, то метод стал короче и понятнее. Каждая полоса прокрутки после изменения размеров выводится на передний план.


Метод, перерисовывающий объект:

//+------------------------------------------------------------------+
//| Перерисовывает объект                                            |
//+------------------------------------------------------------------+
void CContainer::Redraw(bool redraw)
  {
   CWinFormBase::Redraw(redraw);
   this.BringToTopScrollBars();
  }
//+------------------------------------------------------------------+

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


Метод, переносящий полосы прокрутки на передний план:

//+------------------------------------------------------------------+
//| Переносит полосы прокрутки на передний план                      |
//+------------------------------------------------------------------+
void CContainer::BringToTopScrollBars(void)
  {
   CScrollBarHorisontal *sbh=this.GetScrollBarHorisontal();
   CScrollBarVertical   *sbv=this.GetScrollBarVertical();
   if(sbh!=NULL)
      sbh.BringToTop();
   if(sbv!=NULL)
      sbv.BringToTop();
  }
//+------------------------------------------------------------------+

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


Метод, обновляющий координаты:

//+------------------------------------------------------------------+
//| Обновляет координаты                                             |
//+------------------------------------------------------------------+
bool CContainer::Move(const int x,const int y,const bool redraw=false)
  {
   if(!CForm::Move(x,y,redraw))
      return false;
   this.BringToTopScrollBars();
   return true;
  }
//+------------------------------------------------------------------+

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


Теперь нам нужно события объектов-полос прокрутки включить в обработку в классе-коллекции графических элементов.

В файле \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh, в его обработчике событий, в блоке обработки щелчка по объекту впишем такой блок кода для вызова обработчика событий мышки объектов-полос прокрутки:

      //+------------------------------------------------------------------+
      //|  Щелчок по элементу управления                                   |
      //+------------------------------------------------------------------+
      if(idx==WF_CONTROL_EVENT_CLICK)
        {
         //--- Если в dparam записан тип элемента управления TabControl
         if((ENUM_GRAPH_ELEMENT_TYPE)dparam==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)
           {
            //--- В зависимости от типа элемента, сгенерировавшего событие, указываем тип события
            int event_id=
              (object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT   ?  WF_CONTROL_EVENT_CLICK_SCROLL_LEFT  :
               object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT  ?  WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT :
               object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP     ?  WF_CONTROL_EVENT_CLICK_SCROLL_UP    :
               object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN   ?  WF_CONTROL_EVENT_CLICK_SCROLL_DOWN  :
               WF_CONTROL_EVENT_NO_EVENT);
            //--- Если базовый элемент управления получен - вызываем его обработчик событий
            if(base_elm!=NULL)
               base_elm.OnChartEvent(event_id,lparam,dparam,sparam);
           }
         //--- Если базовый объект - горизонтальная или вертикальная полоса прокрутки
         if(base!=NULL && (base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL || base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL))
           {
            //--- В зависимости от типа элемента, сгенерировавшего событие, указываем тип события
            int event_id=
              (object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT   ?  WF_CONTROL_EVENT_CLICK_SCROLL_LEFT  :
               object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT  ?  WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT :
               object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP     ?  WF_CONTROL_EVENT_CLICK_SCROLL_UP    :
               object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN   ?  WF_CONTROL_EVENT_CLICK_SCROLL_DOWN  :
               WF_CONTROL_EVENT_NO_EVENT);
            //--- Вызываем обработчик событий базового элемента
            base.OnChartEvent(event_id,lparam,dparam,sparam);
           }
        }

      //+------------------------------------------------------------------+
      //|  Выбор вкладки элемента управления TabControl                    |
      //+------------------------------------------------------------------+
      if(idx==WF_CONTROL_EVENT_TAB_SELECT)
        {
         if(base!=NULL)
            base.OnChartEvent(idx,lparam,dparam,sparam);
        }

У нас всё готово для тестирования.


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

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

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

//--- Создадим требуемое количество объектов WinForms Panel
   CPanel *pnl=NULL;
   for(int i=0;i<FORMS_TOTAL;i++)
     {
      pnl=engine.CreateWFPanel("WinForms Panel"+(string)i,(i==0 ? 50 : 70),(i==0 ? 50 : 70),410,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false);
      if(pnl!=NULL)
        {
         pnl.Hide();
         //--- Установим значение Padding равным 4
         pnl.SetPaddingAll(3);
         //--- Установим флаги перемещаемости, автоизменения размеров и режим автоизменения из входных параметров
         pnl.SetMovable(InpMovable);
         pnl.SetAutoSize(InpAutoSize,false);
         pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false);

         //---
         pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON,-40,10,pnl.WidthWorkspace()+80,pnl.HeightWorkspace()-30,clrNONE,255,true,false);
         CButton *btn=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,0);
         btn.SetText("123456789012345678901234567890");
         pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LABEL,40,20,60,20,clrDodgerBlue,255,false,false);
         CLabel *lbl=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_LABEL,0);
         lbl.SetText("LABEL");
         /*
         //--- Создадим элемент управления 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)
           {


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


Как видим, прокрутка, управляемая кнопками со стрелками вполне корректно работает. При попытке сместить ползунок мышкой он "сопротивляется", что естественно — у нас ещё нет обработки смещения ползунка, но перерасчёт его размеров и координат уже есть. Соответственно, при попытке смещения ползунка мышкой, метод установки его координат возвращает его в то положение, которое соответствует положению содержимого контейнера в его видимой области. В последующих статьях всё будет доработано до правильного функционирования.



Что дальше

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

К содержанию

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

 
DoEasy. Элементы управления (Часть 26): Дорабатываем WinForms-объект "ToolTip" и начинаем разработку индикатора выполнения "ProgressBar"
DoEasy. Элементы управления (Часть 27): Продолжаем работу над WinForms-объектом "ProgressBar"
DoEasy. Элементы управления (Часть 28): Стили полосы в элементе управления "ProgressBar"
DoEasy. Элементы управления (Часть 29): Вспомогательный элемент управления "ScrollBar"
DoEasy. Элементы управления (Часть 30): Оживляем элемент управления "ScrollBar"