English 中文 Español Deutsch 日本語 Português
preview
DoEasy. Элементы управления (Часть 5): Базовый WinForms-объект, элемент управления "Панель", параметр AutoSize

DoEasy. Элементы управления (Часть 5): Базовый WinForms-объект, элемент управления "Панель", параметр AutoSize

MetaTrader 5Примеры | 19 мая 2022, 14:31
1 084 0
Artyom Trishkin
Artyom Trishkin

Содержание


Концепция

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

Если объект, находящийся внутри панели, имеет активированным свойство Dock, рассмотренное в предыдущей статье, то такой объект "прилипает" к границам своего контейнера. Граница контейнера указывается в свойстве DockMode. При этом, если каждый последующий объект, размещаемый внутри панели, имеет привязку к стороне своего контейнера (панели) такую же, как и у предыдущего, размещённого внутри панели объекта, то он привязывается уже к ближайшей стороне предыдущего объекта, а не к указанной грани контейнера. Таким образом все объекты, размещённые внутри панели с привязкой, например, к левому краю контейнера, будут расположены в один ряд слева-направо. При этом, если у панели активирован режим AutoSize, то контейнер автоматически будет увеличен по ширине так, чтобы все располагаемые внутри него объекты, выстроенные в один ряд, не выходили за пределы своего контейнера. Точно так же должен вести себя контейнер в случае, если внутри него размещается объект, выступающий за края панели — если у панели включен режим AutoSize, то она должна подстроить размер своих сторон так, чтобы объект не выходил за её пределы.

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

Помимо обозначенных задач, проведём небольшую оптимизацию построения графических элементов внутри панели. Так как нам необходимо сначала расположить все привязанные к панели элементы по их порядковым номерам и в соответствии с их значением привязки (Dock), а затем изменить размеры панели в случае, если ей установлено свойство автоматического изменения размера под её внутреннее содержимое, и если это действительно необходимо, то нам нужно сначала "виртуально" расположить все элементы внутри панели, затем поглядеть насколько нужно изменить размер панели, изменить его при необходимости, и только после этого перерисовать заново все элементы, расположенные внутри панели. Если этого не сделать, а сразу же рисовать элементы, то их прорисовка с изменением размера и места привязки становится видна в реальном времени, что отображается в появлении различных артефактов изображения вокруг панели, что некрасиво и неправильно. Поэтому мы сначала будем располагать элементы в нужном порядке, изменять их размеры при необходимости (это зависит от способа привязки элемента и его номера в списке привязанных элементов), затем вычислять насколько нужно изменить размер панели, и только после выполнения всего перечисленного, перерисовывать панель и элементы, вновь расположенные внутри неё в новом порядке.


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

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

//+------------------------------------------------------------------+
//| Список типов объектов библиотеки                                 |
//+------------------------------------------------------------------+
enum ENUM_OBJECT_DE_TYPE
  {
//--- Графика
   OBJECT_DE_TYPE_GBASE =  COLLECTION_ID_LIST_END+1,              // Тип объекта "Базовый объект всех графических объектов библиотеки"
   OBJECT_DE_TYPE_GELEMENT,                                       // Тип объекта "Графический элемент"
   OBJECT_DE_TYPE_GFORM,                                          // Тип объекта "Форма"
   OBJECT_DE_TYPE_GFORM_CONTROL,                                  // Тип объекта "Форма управления опорными точками графического объекта"
   OBJECT_DE_TYPE_GSHADOW,                                        // Тип объекта "Тень"
   //--- WinForms
   OBJECT_DE_TYPE_GWF_BASE,                                       // Тип объекта "WinForms Base" (базовый абстрактный WinForms-объект)
   OBJECT_DE_TYPE_GWF_PANEL,                                      // Тип объекта "WinForms Panel"
//--- Анимация
   OBJECT_DE_TYPE_GFRAME,                                         // Тип объекта "Один кадр анимации"
//--- ...
//--- ...
  }


В список типов графических элементов добавим два новых типа — графический элемент-подложку и базовый WinForms-объект, а макроподстановку GRAPH_ELEMENT_TYPE_PANEL переименуем в GRAPH_ELEMENT_TYPE_WF_PANEL:

//+------------------------------------------------------------------+
//| Список типов графических элементов                               |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_STANDARD,                       // Стандартный графический объект
   GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED,              // Расширенный стандартный графический объект
   GRAPH_ELEMENT_TYPE_SHADOW_OBJ,                     // Объект тени
   GRAPH_ELEMENT_TYPE_ELEMENT,                        // Элемент
   GRAPH_ELEMENT_TYPE_FORM,                           // Форма
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Окно
   //--- WinForms
   GRAPH_ELEMENT_TYPE_WF_UNDERLAY,                    // Подложка объекта-панели
   GRAPH_ELEMENT_TYPE_WF_BASE,                        // Windows Forms Base
   GRAPH_ELEMENT_TYPE_WF_PANEL,                       // Windows Forms Panel
  };
//+------------------------------------------------------------------+

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

В файле \MQL5\Include\DoEasy\Data.mqh впишем индексы новых сообщений библиотеки:

   MSG_GRAPH_ELEMENT_TYPE_WINDOW,                     // Окно
   MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY,                // Подложка объекта-элемента управления WinForms "Панель"
   MSG_GRAPH_ELEMENT_TYPE_WF_BASE,                    // Базовый элемент управления WinForms
   MSG_GRAPH_ELEMENT_TYPE_WF_PANEL,                   // Элемент управления Panel

и текстовые сообщения, соответствующие вновь добавленным индексам:

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


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

//+------------------------------------------------------------------+
//| Возвращает описание типа графического элемента                   |
//+------------------------------------------------------------------+
string CGBaseObj::TypeElementDescription(void)
  {
   return
     (
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD)           :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)  :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_ELEMENT            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT)            :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_SHADOW_OBJ         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ)         :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM)               :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WINDOW             ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW)             :
      //---
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY)        :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_BASE            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE)            :
      this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_PANEL           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL)           :
      "Unknown"
     );
  }
//+------------------------------------------------------------------+


В классе объекта графического элемента на канвасе, от которого унаследованы остальные Canvas-объекты графики библиотеки, сделаем некоторые методы виртуальными. Класс расположен в файле \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh.
Дело в том, что не для каждого объекта-наследника этого класса может подойти созданная в нём реализация некоторых методов. Сделав же их здесь виртуальными, мы даём возможность изменить эти методы в тех классах-наследниках, где реализация методов должна отличаться от созданной в этом классе.

//+------------------------------------------------------------------+
//| Методы упрощённого доступа к свойствам объекта                   |
//+------------------------------------------------------------------+
//--- Устанавливает координату (1) X, (2) Y, (3) ширину, (4) высоту, (5) правый, (6) нижний край элемента,
   virtual bool      SetCoordX(const int coord_x);
   virtual bool      SetCoordY(const int coord_y);
   virtual bool      SetWidth(const int width);
   virtual bool      SetHeight(const int height);
   void              SetRightEdge(void)                        { this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge());           }
   void              SetBottomEdge(void)                       { this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge());         }

...

//+------------------------------------------------------------------+
//| Методы заполнения, очистки и обновления растровых данных         |
//+------------------------------------------------------------------+
//--- Очищает элемент с заполнением его цветом и непрозрачностью
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Очищает элемент заливкой градиентом
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
//--- Полностью очищает элемент
   virtual void      Erase(const bool redraw=false);
//--- Обновляет элемент
   void              Update(const bool redraw=false)           { this.m_canvas.Update(redraw);                                         }

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

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

В файле класса объекта-тени \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh поменяем названия переменных и методов — уберём из них текст "shadow". Просто считаю нахождение этой подстроки в именах переменных и названиях методов объекта-тени излишним.

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

//+------------------------------------------------------------------+
//| Класс объекта Тени                                               |
//+------------------------------------------------------------------+
class CShadowObj : public CGCnvElement
  {
private:
   color             m_color;                         // Цвет тени
   uchar             m_opacity;                       // Непрозрачность тени
   uchar             m_blur;                          // Размытие
//--- Размытие по-Гауссу
   bool              GaussianBlur(const uint radius);
//--- Возвращает массив весовых коэффициентов
   bool              GetQuadratureWeights(const double mu0,const int n,double &weights[]);
//--- Рисует форму тени объекта
   void              DrawShadowFigureRect(const int w,const int h);

public:
//--- Конструктор
                     CShadowObj(const long chart_id,
                                const int subwindow,
                                const string name,
                                const int x,
                                const int y,
                                const int w,
                                const int h);

//--- Поддерживаемые свойства объекта (1) целочисленные, (2) строковые
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)  { return true; }
   
//--- Рисует тень объекта
   void              Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw);
   
//+------------------------------------------------------------------+
//| Методы упрощённого доступа к свойствам объекта                   |
//+------------------------------------------------------------------+
//--- (1) Устанавливает, (2) возвращает цвет тени
   void              SetColor(const color colour)                             { this.m_color=colour;     }
   color             Color(void)                                        const { return this.m_color;     }
//--- (1) Устанавливает, (2) возвращает непрозрачность тени
   void              SetOpacity(const uchar opacity)                          { this.m_opacity=opacity;  }
   uchar             Opacity(void)                                      const { return this.m_opacity;   }
//--- (1) Устанавливает, (2) возвращает размытие тени
   void              SetBlur(const uchar blur)                                { this.m_blur=blur;        }
   uchar             Blur(void)                                         const { return this.m_blur;      }
  };
//+------------------------------------------------------------------+


В конструкторе класса впишем значение непрозрачности тени по умолчанию, заданное макроподстановкой CLR_DEF_SHADOW_OPACITY в файле Defines.mqh библиотеки, и укажем величину размытия тени по умолчанию тоже макроподстановкой DEF_SHADOW_BLUR из того же файла:

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


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

//+------------------------------------------------------------------+
//| Рисует тень объекта                                              |
//+------------------------------------------------------------------+
void CShadowObj::Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw)
  {
//--- Устанавливаем в переменные значения смещения тени по осям X и Y
   this.SetCoordXRelative(shift_x);
   this.SetCoordYRelative(shift_y);
//--- Рассчитываем ширину и высоту рисуемого прямоугольника
   int w=this.Width()-OUTER_AREA_SIZE*2;
   int h=this.Height()-OUTER_AREA_SIZE*2;
//--- Рисуем закрашенный прямоугольник с рассчитанными размерами
   this.DrawShadowFigureRect(w,h);
//--- Рассчитываем радиус размытия, который не может быть больше четверти размера константы OUTER_AREA_SIZE
   this.m_blur=(blur_value>OUTER_AREA_SIZE/4 ? OUTER_AREA_SIZE/4 : blur_value);
//--- Если размыть фигуру не удалось - уходим из метода (ошибку в журнал выведет GaussianBlur())
   if(!this.GaussianBlur(this.m_blur))
      return;
//--- Смещаем объект тени на указанные в аргументах метода смещения по X и Y и обновляем канвас
   CGCnvElement::Move(this.CoordX()+this.CoordXRelative(),this.CoordY()+this.CoordYRelative(),redraw);
   CGCnvElement::Update(redraw);
  }
//+------------------------------------------------------------------+


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

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

//+------------------------------------------------------------------+
//| Класс объекта "форма"                                            |
//+------------------------------------------------------------------+
class CForm : public CGCnvElement
  {
private:
   CArrayObj         m_list_elements;                          // Список присоединённых элементов
   CAnimations      *m_animations;                             // Указатель на объект анимаций
   CShadowObj       *m_shadow_obj;                             // Указатель на объект тени
   CMouseState       m_mouse;                                  // Объект класса "Состояния мышки"
   ENUM_MOUSE_FORM_STATE m_mouse_form_state;                   // Состояние мышки относительно формы
   ushort            m_mouse_state_flags;                      // Флаги состояния мышки
   color             m_color_frame;                            // Цвет рамки формы
   int               m_offset_x;                               // Смещение координаты X относительно курсора
   int               m_offset_y;                               // Смещение координаты Y относительно курсора
   int               m_init_x;                                 // Координата X формы при её создании
   int               m_init_y;                                 // Координата Y формы при её создании
   int               m_init_w;                                 // Ширина формы при её создании
   int               m_init_h;                                 // Высота формы при её создании

//--- Сбрасывает размер массива (1) текстовых, (2) прямоугольных, (3) геометрических кадров анимаций
   void              ResetArrayFrameT(void);
   void              ResetArrayFrameQ(void);
   void              ResetArrayFrameG(void);
   
//--- Создаёт новый графический объект

Для оптимизации кода метод CreateNewElement() разобъём на два — создадим ещё один метод CreateAndAddNewElement(), где будет создаваться новый элемент и добавляться в список. Объявим этот метод в защищённой секции класса, а в публичную секцию перенесём из класса CPanel методы для работы с переменными, хранящими изначальные координаты и размеры объекта:

protected:
   CArrayObj         m_list_tmp;                               // Список для размещения указателей
   int               m_frame_width_left;                       // Ширина рамки формы слева
   int               m_frame_width_right;                      // Ширина рамки формы справа
   int               m_frame_width_top;                        // Ширина рамки формы сверху
   int               m_frame_width_bottom;                     // Ширина рамки формы снизу
//--- Инициализирует переменные
   void              Initialize(void);
   void              Deinitialize(void);
//--- Создаёт объект для тени
   void              CreateShadowObj(const color colour,const uchar opacity);
//--- Возвращает имя зависимого объекта
   string            CreateNameDependentObject(const string base_name)  const
                       { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name;   }
//--- Обновляет координаты привязанных объектов
   virtual bool      MoveDependentObj(const int x,const int y,const bool redraw=false);
//--- Создаёт новый присоединённый элемент и добавляет его в список присоединённых объектов
   CGCnvElement     *CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                            CGCnvElement *main,
                                            const int x,
                                            const int y,
                                            const int w,
                                            const int h,
                                            const color colour,
                                            const uchar opacity,
                                            const bool activity);
   
public:
//--- Возвращает начальную координату (1) X, (2) Y, (3) ширину, (4) высоту формы
   int               GetCoordXInit(void)           const { return this.m_init_x;             }
   int               GetCoordYInit(void)           const { return this.m_init_y;             }
   int               GetWidthInit(void)            const { return this.m_init_w;             }
   int               GetHeightInit(void)           const { return this.m_init_h;             }
//--- Устанавливает начальную координату (1) X, (2) Y, (3) ширину, (4) высоту формы
   void              SetCoordXInit(const int value)      { this.m_init_x=value;              }
   void              SetCoordYInit(const int value)      { this.m_init_y=value;              }
   void              SetWidthInit(const int value)       { this.m_init_w=value;              }
   void              SetHeightInit(const int value)      { this.m_init_h=value;              }
//--- Возвращает (1) состояние мышки относительно формы, координату (2) X, (3) Y курсора
   ENUM_MOUSE_FORM_STATE MouseFormState(const int id,const long lparam,const double dparam,const string sparam);
   int               MouseCursorX(void)            const { return this.m_mouse.CoordX();     }
   int               MouseCursorY(void)            const { return this.m_mouse.CoordY();     }


Сам метод CreateNewElement() сделаем виртуальным и добавим в него флаг необходимости отрисовки вновь созданного объекта:

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

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


В блоке методов упрощённого доступа к свойствам объекта, объявим два метода для работы со свойством "Размытие тени":

//+------------------------------------------------------------------+
//| Методы упрощённого доступа к свойствам объекта                   |
//+------------------------------------------------------------------+
//--- (1) Устанавливает, (2) возвращает цвет рамки формы
   void              SetColorFrame(const color colour)                        { this.m_color_frame=colour;     }
   color             ColorFrame(void)                                   const { return this.m_color_frame;     }
//--- (1) Устанавливает, (2) возвращает цвет тени формы
   void              SetColorShadow(const color colour);
   color             ColorShadow(void) const;
//--- (1) Устанавливает, (2) возвращает непрозрачность тени формы
   void              SetOpacityShadow(const uchar opacity);
   uchar             OpacityShadow(void) const;
//--- (1) Устанавливает, (2) возвращает размытие тени формы
   void              SetBlurShadow(const uchar blur);
   uchar             BlurShadow(void) const;

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


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

//+------------------------------------------------------------------+
//| Конструктор с указанием идентификатора чарта и подокна           |
//+------------------------------------------------------------------+
CForm::CForm(const long chart_id,
             const int subwindow,
             const string name,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,chart_id,subwindow,name,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GFORM; 
   this.Initialize();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+
//| Конструктор на текущем чарте с указанием подокна                 |
//+------------------------------------------------------------------+
CForm::CForm(const int subwindow,
             const string name,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,::ChartID(),subwindow,name,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GFORM; 
   this.Initialize();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+
//| Конструктор на текущем чарте в главном окне графика              |
//+------------------------------------------------------------------+
CForm::CForm(const string name,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,::ChartID(),0,name,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GFORM; 
   this.Initialize();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+


В самом же методе инициализации в эти значения впишем нули:

//+------------------------------------------------------------------+
//| Инициализирует переменные                                        |
//+------------------------------------------------------------------+
void CForm::Initialize(void)
  {
   this.m_list_elements.Clear();
   this.m_list_elements.Sort();
   this.m_list_tmp.Clear();
   this.m_list_tmp.Sort();
   this.m_shadow_obj=NULL;
   this.m_shadow=false;
   this.m_frame_width_right=DEF_FRAME_WIDTH_SIZE;
   this.m_frame_width_left=DEF_FRAME_WIDTH_SIZE;
   this.m_frame_width_top=DEF_FRAME_WIDTH_SIZE;
   this.m_frame_width_bottom=DEF_FRAME_WIDTH_SIZE;
   this.m_gradient_v=true;
   this.m_gradient_c=false;
   this.m_mouse_state_flags=0;
   this.m_offset_x=0;
   this.m_offset_y=0;
   this.m_init_x=0;
   this.m_init_y=0;
   this.m_init_w=0;
   this.m_init_h=0;
   CGCnvElement::SetInteraction(false);
   this.m_animations=new CAnimations(CGCnvElement::GetObject());
   this.m_list_tmp.Add(m_animations);
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Создаёт новый присоединённый элемент                             |
//| и добавляет его в список присоединённых объектов                 |
//+------------------------------------------------------------------+
CGCnvElement *CForm::CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                            CGCnvElement *main,
                                            const int x,
                                            const int y,
                                            const int w,
                                            const int h,
                                            const color colour,
                                            const uchar opacity,
                                            const bool activity)
  {
//--- Если тип создаваемого графического элемента меньше, чем "элемент" - сообщаем об этом и возвращаем false
   if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),19));
      return NULL;
     }
//--- Задаём номер элемента в списке
   int num=this.m_list_elements.Total();
//--- Создаём имя графического элемента
   string ns=(::StringLen((string)num)<2 ? ::IntegerToString(num,2,'0') : (string)num);
   string name="Elm"+ns;
//--- Получаем экранные координаты объекта относительно системы координат базового объекта
   int elm_x=x;
   int elm_y=y;
   this.GetCoords(elm_x,elm_y);
//--- Создаём новый графический элемент
   CGCnvElement *obj=this.CreateNewGObject(element_type,num,name,elm_x,elm_y,w,h,colour,opacity,false,activity);
   if(obj==NULL)
      return NULL;
//--- и добавляем его в список привязанных графических элементов
   if(!this.AddNewElement(obj,elm_x,elm_y))
     {
      delete obj;
      return NULL;
     }
//--- Устанавливаем минимальные свойства привязанному графическому элементу
   obj.SetColorBackground(colour);
   obj.SetOpacity(opacity);
   obj.SetActive(activity);
   obj.SetMain(main);
   obj.SetBase(this.GetObject());
   obj.SetID(this.ID());
   obj.SetNumber(num);
   obj.SetCoordXRelative(x);
   obj.SetCoordYRelative(y);
   obj.SetZorder(this.Zorder(),false);
   obj.SetCoordXRelativeInit(x);
   obj.SetCoordYRelativeInit(y);
   return obj;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Создаёт новый присоединённый элемент                             |
//+------------------------------------------------------------------+
bool CForm::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                             CGCnvElement *main,
                             const int x,
                             const int y,
                             const int w,
                             const int h,
                             const color colour,
                             const uchar opacity,
                             const bool activity,
                             const bool redraw)
  {
//--- Создаём новый графический элемент
   CGCnvElement *obj=this.CreateAndAddNewElement(element_type,main,x,y,w,h,colour,opacity,activity);
//--- Если объект создан - рисуем добавленный объект и возвращаем true
   if(obj==NULL)
      return false;
   obj.Erase(colour,opacity,redraw);
   return true;
  }
//+------------------------------------------------------------------+


В методе, рисующем тень, теперь нужно передавать флаг необходимости прорисовки тени в методе Draw() класса объекта тени:

//+------------------------------------------------------------------+
//| Рисует тень                                                      |
//+------------------------------------------------------------------+
void CForm::DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=DEF_SHADOW_BLUR)
  {
//--- Если флаг тени выключен - уходим
   if(!this.m_shadow)
      return;
//--- Если объект тени не создан - создадим его
   if(this.m_shadow_obj==NULL)
      this.CreateShadowObj(colour,opacity);
//--- Если объект тени существует, рисуем на нём тень,
//--- устанавливаем флаг видимости объекта-тени и
//--- перемещаем объект-форму на передний план
   if(this.m_shadow_obj!=NULL)
     {
      this.m_shadow_obj.Draw(shift_x,shift_y,blur,true);
      this.m_shadow_obj.SetVisible(true,false);
      this.BringToTop();
     }
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Устанавливает размытие тени формы                                |
//+------------------------------------------------------------------+
void CForm::SetBlurShadow(const uchar blur)
  {
   if(this.m_shadow_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT));
      return;
     }
   this.m_shadow_obj.SetBlur(blur);
  }
//+------------------------------------------------------------------+

Если объекта-тени не существует, выводится сообщение, что сначала нужно создать объект тени, иначе — вызывается метод установки величины размытия в объект тени.

Метод, возвращающий размытие тени формы:

//+------------------------------------------------------------------+
//| Возвращает размытие тени формы                                   |
//+------------------------------------------------------------------+
uchar CForm::BlurShadow(void) const
  {
   if(this.m_shadow_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT));
      return 0;
     }
   return this.m_shadow_obj.Blur();
  }
//+------------------------------------------------------------------+

Если объекта-тени не существует, выводится сообщение, что сначала нужно создать объект тени, иначе — возвращается значение величины размытия объекта тени.

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


Класс базового WinForms-объекта

В папке библиотеки \MQL5\Include\DoEasy\Objects\Graph\WForms\ создадим новый файл WinFormBase.mqh класса CWinFormBase. Класс должен быть унаследован от класса объекта-формы CForm. Для того, чтобы базовый класс был виден в этом файле, подключим к нему файл класса объекта-формы:

//+------------------------------------------------------------------+
//|                                                  WinFormBase.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\Form.mqh"
//+------------------------------------------------------------------+
//| Класс объекта "форма"                                            |
//+------------------------------------------------------------------+
class CWinFormBase : public CForm
  {
  }

В этот класс перенесём переменные и методы для работы с ними из файла класса WinForms-объекта "Панель" из файла \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh. В принципе, здесь будут находиться все переменные и методы, которые мы писали для объекта-панели. Ну и, конечно же, добавим новые.

В защищённой секции класса расположим такие переменные:

//+------------------------------------------------------------------+
//| Класс объекта "форма"                                            |
//+------------------------------------------------------------------+
class CWinFormBase : public CForm
  {
protected:
   color             m_fore_color;                                   // Цвет текста по умолчанию для всех объектов элемента управления
   ENUM_FW_TYPE      m_bold_type;                                    // Тип толщины шрифта
   ENUM_FRAME_STYLE  m_border_style;                                 // Стиль рамки элемента управления
   bool              m_autosize;                                     // Флаг автоматического изменения размера элемента управления под содержимое
   ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode;                          // Режим привязки границ элемента управления к контейнеру
   int               m_margin[4];                                    // Массив промежутков всех сторон между полями данного и другого элемента управления
   int               m_padding[4];                                   // Массив промежутков всех сторон внутри элемента управления

private:

В приватной секции расположим метод, возвращающий флаги шрифта:

private:
//--- Возвращает флаги шрифта
   uint              GetFontFlags(void);

public:

а в публичной секции объявим виртуальные методы для очистки элемента, его перерисовки и изменения размеров, конструкторы класса, и перенесём из файла класса CPanel такие методы:

public:
//--- Очищает элемент с заполнением его цветом и непрозрачностью
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Очищает элемент заливкой градиентом
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
//--- Полностью очищает элемент
   virtual void      Erase(const bool redraw=false);
//--- Перерисовывает объект
   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);

//--- Конструкторы
                     CWinFormBase(const long chart_id,
                                  const int subwindow,
                                  const string name,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
                     CWinFormBase(const string name) : CForm(::ChartID(),0,name,0,0,0,0)
                       {
                        this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
                       }
                       
//--- (1) Устанавливает, (2) возвращает цвет текста по умолчанию всех объектов на панели
   void              SetForeColor(const color clr)                   { this.m_fore_color=clr;               }
   color             ForeColor(void)                           const { return this.m_fore_color;            }

//--- (1) Устанавливает, (2) возвращает флаг шрифта Bold
   void              SetBold(const bool flag);
   bool              Bold(void);
//--- (1) Устанавливает, (2) возвращает флаг шрифта Italic
   void              SetItalic(const bool flag);
   bool              Italic(void);
//--- (1) Устанавливает, (2) возвращает флаг шрифта Strikeout
   void              SetStrikeout(const bool flag);
   bool              Strikeout(void);
//--- (1) Устанавливает, (2) возвращает флаг шрифта Underline
   void              SetUnderline(const bool flag);
   bool              Underline(void);
//--- (1) Устанавливает, (2) возвращает стиль начертания шрифта
   void              SetFontDrawStyle(ENUM_FONT_STYLE style);
   ENUM_FONT_STYLE   FontDrawStyle(void);
//--- (1) Устанавливает, (2) возвращает тип толщины шрифта
   void              SetFontBoldType(ENUM_FW_TYPE type);
   ENUM_FW_TYPE      FontBoldType(void)                        const { return this.m_bold_type;             }
//--- (1) Устанавливает, (2) возвращает стиль рамки
   void              SetBorderStyle(const ENUM_FRAME_STYLE style)    { this.m_border_style=style;           }
   ENUM_FRAME_STYLE  BorderStyle(void)                         const { return this.m_border_style;          }

//--- (1) Устанавливает, (2) возвращает флаг автоматического изменения размера элемента под содержимое
   virtual void      SetAutoSize(const bool flag,const bool redraw)  { this.m_autosize=flag;                }
   bool              AutoSize(void)                                  { return this.m_autosize;              }
   
//--- (1) Устанавливает, (2) возвращает режим привязки границ элемента к контейнеру
   virtual void      SetDockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode,const bool redraw)
                       {
                        this.m_dock_mode=mode;
                       }
   ENUM_CANV_ELEMENT_DOCK_MODE DockMode(void)                  const { return this.m_dock_mode;             }
   
//--- Устанавливает промежуток (1) слева, (2) сверху, (3) справа, (4) снизу, (5) со всех сторон между полями данного и другого элемента управления
   void              SetMarginLeft(const int value)                  { this.m_margin[0]=value;              }
   void              SetMarginTop(const int value)                   { this.m_margin[1]=value;              }
   void              SetMarginRight(const int value)                 { this.m_margin[2]=value;              }
   void              SetMarginBottom(const int value)                { this.m_margin[3]=value;              }
   void              SetMarginAll(const int value)
                       {
                        this.SetMarginLeft(value); this.SetMarginTop(value); this.SetMarginRight(value); this.SetMarginBottom(value);
                       }
//--- Возвращает промежуток (1) слева, (2) сверху, (3) справа, (4) снизу между полями данного и другого элемента управления
   int               MarginLeft(void)                          const { return this.m_margin[0];             }
   int               MarginTop(void)                           const { return this.m_margin[1];             }
   int               MarginRight(void)                         const { return this.m_margin[2];             }
   int               MarginBottom(void)                        const { return this.m_margin[3];             }

//--- Устанавливает промежуток (1) слева, (2) сверху, (3) справа, (4) снизу, (5) со всех сторон внутри элемента управления
   virtual void      SetPaddingLeft(const uint value)
                       {
                        this.m_padding[0]=((int)value<this.m_frame_width_left ? this.m_frame_width_left : (int)value);
                       }
   virtual void      SetPaddingTop(const uint value)
                       {
                        this.m_padding[1]=((int)value<this.m_frame_width_top ? this.m_frame_width_top : (int)value);
                       }
   virtual void      SetPaddingRight(const uint value)
                       {
                        this.m_padding[2]=((int)value<this.m_frame_width_right ? this.m_frame_width_right : (int)value);
                       }
   virtual void      SetPaddingBottom(const uint value)
                       {
                        this.m_padding[3]=((int)value<this.m_frame_width_bottom ? this.m_frame_width_bottom : (int)value);
                       }
   virtual void      SetPaddingAll(const uint value)
                       {
                        this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value);
                       }
   
//--- Устанавливает ширину рамки элемента (1) слева, (2) сверху, (3) справа, (4) снизу
   virtual void      SetFrameWidthLeft(const uint value)             { this.m_frame_width_left=(int)value;  }
   virtual void      SetFrameWidthTop(const uint value)              { this.m_frame_width_top=(int)value;   }
   virtual void      SetFrameWidthRight(const uint value)            { this.m_frame_width_right=(int)value; }
   virtual void      SetFrameWidthBottom(const uint value)           { this.m_frame_width_bottom=(int)value;}
   virtual void      SetFrameWidthAll(const uint value)
                       {
                        this.SetFrameWidthLeft(value); this.SetFrameWidthTop(value); this.SetFrameWidthRight(value); this.SetFrameWidthBottom(value);
                       }
   
//--- Возвращает ширину рамки элемента (1) слева, (2) сверху, (3) справа, (4) снизу
   int               FrameWidthLeft(void)                      const { return this.m_frame_width_left;      }
   int               FrameWidthTop(void)                       const { return this.m_frame_width_top;       }
   int               FrameWidthRight(void)                     const { return this.m_frame_width_right;     }
   int               FrameWidthBottom(void)                    const { return this.m_frame_width_bottom;    }
   
//--- Возвращает промежуток (1) слева, (2) сверху, (3) справа, (4) снизу между полями внутри элемента управления
   int               PaddingLeft(void)                         const { return this.m_padding[0];            }
   int               PaddingTop(void)                          const { return this.m_padding[1];            }
   int               PaddingRight(void)                        const { return this.m_padding[2];            }
   int               PaddingBottom(void)                       const { return this.m_padding[3];            }
   
  };
//+------------------------------------------------------------------+

Большинство перенесённых методов сделаем виртуальными — чтобы можно было в наследуемых классах переопределить их, если будет такая необходимость. Все методы для установки свойств теперь имеют в своём названии приставку "Set". Это однозначно указывает на принадлежность метода.

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

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CWinFormBase::CWinFormBase(const long chart_id,
                           const int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CForm(chart_id,subwindow,name,x,y,w,h)
  {
//--- Установим тип графического элемента и тип объекта библиотеки как базовый WinForms-объект
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE);
   this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
//--- Инициализируем все переменные
   this.m_fore_color=CLR_DEF_FORE_COLOR;
   this.m_bold_type=FW_TYPE_NORMAL;
   this.SetMarginAll(0);
   this.SetPaddingAll(0);
   this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetAutoSize(false,false);
   CForm::SetCoordXInit(x);
   CForm::SetCoordYInit(y);
   CForm::SetWidthInit(w);
   CForm::SetHeightInit(h);
   this.m_shadow=false;
   this.m_frame_width_right=0;
   this.m_frame_width_left=0;
   this.m_frame_width_top=0;
   this.m_frame_width_bottom=0;
   this.m_gradient_v=true;
   this.m_gradient_c=false;
  }
//+------------------------------------------------------------------+

Методы, перенесённые из класса CPanel:

//+------------------------------------------------------------------+
//| Возвращает флаги шрифта                                          |
//+------------------------------------------------------------------+
uint CWinFormBase::GetFontFlags(void)
  {
   string name;
   int size;
   uint flags;
   uint angle;
   CGCnvElement::GetFont(name,size,flags,angle);
   return flags;
  }
//+------------------------------------------------------------------+
//| Устанавливает флаг шрифта Bold                                   |
//+------------------------------------------------------------------+
void CWinFormBase::SetBold(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
     {
      this.m_bold_type=FW_TYPE_BOLD;
      CGCnvElement::SetFontFlags(flags | FW_BOLD);
     }
   else
      this.m_bold_type=FW_TYPE_NORMAL;
  }
//+------------------------------------------------------------------+
//| Возвращает флаг шрифта Bold                                      |
//+------------------------------------------------------------------+
bool CWinFormBase::Bold(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FW_BOLD)==FW_BOLD;
  }
//+------------------------------------------------------------------+
//| Устанавливает флаг шрифта Italic                                 |
//+------------------------------------------------------------------+
void CWinFormBase::SetItalic(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
      CGCnvElement::SetFontFlags(flags | FONT_ITALIC);
  }
//+------------------------------------------------------------------+
//| Возвращает флаг шрифта Italic                                    |
//+------------------------------------------------------------------+
bool CWinFormBase::Italic(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FONT_ITALIC)==FONT_ITALIC;
  }
//+------------------------------------------------------------------+
//| Устанавливает флаг шрифта Strikeout                              |
//+------------------------------------------------------------------+
void CWinFormBase::SetStrikeout(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
      CGCnvElement::SetFontFlags(flags | FONT_STRIKEOUT);
  }
//+------------------------------------------------------------------+
//| Возвращает флаг шрифта Strikeout                                 |
//+------------------------------------------------------------------+
bool CWinFormBase::Strikeout(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FONT_STRIKEOUT)==FONT_STRIKEOUT;
  }
//+------------------------------------------------------------------+
//| Устанавливает флаг шрифта Underline                              |
//+------------------------------------------------------------------+
void CWinFormBase::SetUnderline(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
      CGCnvElement::SetFontFlags(flags | FONT_UNDERLINE);
  }
//+------------------------------------------------------------------+
//| Возвращает флаг шрифта Underline                                 |
//+------------------------------------------------------------------+
bool CWinFormBase::Underline(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FONT_UNDERLINE)==FONT_UNDERLINE;
  }
//+------------------------------------------------------------------+
//| Устанавливает стиль начертания шрифта                            |
//+------------------------------------------------------------------+
void CWinFormBase::SetFontDrawStyle(ENUM_FONT_STYLE style)
  {
   switch(style)
     {
      case FONT_STYLE_ITALIC     :  this.SetItalic(true);      break;
      case FONT_STYLE_UNDERLINE  :  this.SetUnderline(true);   break;
      case FONT_STYLE_STRIKEOUT  :  this.SetStrikeout(true);   break;
      default: break;
     }
  }
//+------------------------------------------------------------------+
//| Возвращает стиль начертания шрифта                               |
//+------------------------------------------------------------------+
ENUM_FONT_STYLE CWinFormBase::FontDrawStyle(void)
  {
   return
     (
      this.Italic()     ?  FONT_STYLE_ITALIC    :
      this.Underline()  ?  FONT_STYLE_UNDERLINE :
      this.Strikeout()  ?  FONT_STYLE_UNDERLINE :
      FONT_STYLE_NORMAL
     );
  }
//+------------------------------------------------------------------+
//| Устанавливает тип толщины шрифта                                 |
//+------------------------------------------------------------------+
void CWinFormBase::SetFontBoldType(ENUM_FW_TYPE type)
  {
   this.m_bold_type=type;
   uint flags=this.GetFontFlags();
   switch(type)
     {
      case FW_TYPE_DONTCARE   : CGCnvElement::SetFontFlags(flags | FW_DONTCARE);    break;
      case FW_TYPE_THIN       : CGCnvElement::SetFontFlags(flags | FW_THIN);        break;
      case FW_TYPE_EXTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_EXTRALIGHT);  break;
      case FW_TYPE_ULTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_ULTRALIGHT);  break;
      case FW_TYPE_LIGHT      : CGCnvElement::SetFontFlags(flags | FW_LIGHT);       break;
      case FW_TYPE_REGULAR    : CGCnvElement::SetFontFlags(flags | FW_REGULAR);     break;
      case FW_TYPE_MEDIUM     : CGCnvElement::SetFontFlags(flags | FW_MEDIUM);      break;
      case FW_TYPE_SEMIBOLD   : CGCnvElement::SetFontFlags(flags | FW_SEMIBOLD);    break;
      case FW_TYPE_DEMIBOLD   : CGCnvElement::SetFontFlags(flags | FW_DEMIBOLD);    break;
      case FW_TYPE_BOLD       : CGCnvElement::SetFontFlags(flags | FW_BOLD);        break;
      case FW_TYPE_EXTRABOLD  : CGCnvElement::SetFontFlags(flags | FW_EXTRABOLD);   break;
      case FW_TYPE_ULTRABOLD  : CGCnvElement::SetFontFlags(flags | FW_ULTRABOLD);   break;
      case FW_TYPE_HEAVY      : CGCnvElement::SetFontFlags(flags | FW_HEAVY);       break;
      case FW_TYPE_BLACK      : CGCnvElement::SetFontFlags(flags | FW_BLACK);       break;
      default                 : CGCnvElement::SetFontFlags(flags | FW_NORMAL);      break;
     }
  }
//+------------------------------------------------------------------+

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


Виртуальные методы очистки (закрашивания цветом) элемента:

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

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


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

//+------------------------------------------------------------------+
//| Устанавливает новые размеры текущему объекту                     |
//+------------------------------------------------------------------+
bool CWinFormBase::Resize(const int w,const int h,const bool redraw)
  {
//--- Если ширина и высота объекта равны переданным - возвращаем true
   if(this.Width()==w && this.Height()==h)
      return true;
//--- Объявляем переменную с результатом изменения свойства
   bool res=true;
//--- Запоминаем изначальные размеры панели
   int prev_w=this.Width();
   int prev_h=this.Height();
//--- В переменную res записываем результат изменения свойств
//--- (если величина свойства не равна переданной величине)
   if(this.Width()!=w)
      res &=this.SetWidth(w);
   if(this.Height()!=h)
      res &=this.SetHeight(h);
   if(!res)
      return false;
//--- Рассчитываем величину, на которую нужно изменить размеры
   int excess_w=this.Width()-prev_w;
   int excess_h=this.Height()-prev_h;
//--- Получим объект "Тень"
   CShadowObj *shadow=this.GetShadowObj();
//--- Если у объекта есть тень, и объект "Тень" получен
   if(this.IsShadow() && shadow!=NULL)
     {
      //--- запомним смещения тени по X и Y,
      int x=shadow.CoordXRelative();
      int y=shadow.CoordYRelative();
      //--- установим новые ширину и высоту тени
      res &=shadow.SetWidth(shadow.Width()+excess_w);
      res &=shadow.SetHeight(shadow.Height()+excess_h);
      if(!res)
         return false;
      //--- Если не нужно перерисовывать - сотрём тень
      if(!redraw)
         shadow.Erase();
      //--- Запишем ранее сохранённые значения смещения тени относительно панели
      shadow.SetCoordXRelative(x);
      shadow.SetCoordYRelative(y);
     }
//--- Перерисуем элемент с новыми размерами
   if(redraw)
      this.Redraw(true);
//--- Всё успешно - возвращаем true
   return true;
  }
//+------------------------------------------------------------------+

Логика метода подробно расписана в комментариях к коду. Вкратце: если в метод переданы существующие размеры объекта, то менять ничего не нужно — сразу возвращаем true. Если же переданные в метод размеры не совпадают с теми, что сейчас есть у объекта, то меняем их. Если есть тень, то меняем и её размер, и затем перерисовываем весь объект в случае, если установлен флаг перерисовки. Т.е., если флаг перерисовки не установлен, то меняются размеры объекта, но сам он не перерисовывается. Это нужно для ускорения перерисовки множества объектов, привязанных к другому. Только после изменения размеров всех связанных объектов их нужно все перерисовать.

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

//+------------------------------------------------------------------+
//| Устанавливает новые размеры указанному по индексу объекту        |
//+------------------------------------------------------------------+
bool CWinFormBase::Resize(const int index,const int w,const int h,const bool redraw)
  {
   CWinFormBase *obj=this.GetElement(index);
   return(obj!=NULL ? obj.Resize(w,h,redraw) : false);
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Перерисовывает объект                                            |
//+------------------------------------------------------------------+
void CWinFormBase::Redraw(bool redraw)
  {
//--- Если тип объекта меньше, чем "Базовый WinForms-объект" - уходим
   if(this.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE)
      return;
//--- Получим объект "Тень"
   CShadowObj *shadow=this.GetShadowObj();
//--- Если у объекта есть тень, и объект "Тень" существует - перерисуем тень
   if(this.IsShadow() && shadow!=NULL)
     {
      //--- сотрём ранее нарисованную тень,
      shadow.Erase();
      //--- запомним относительные координаты тени,
      int x=shadow.CoordXRelative();
      int y=shadow.CoordYRelative();
      //--- перерисуем тень,
      if(redraw)
         shadow.Draw(0,0,shadow.Blur(),redraw);
      //--- восстановим относительные координаты тени
      shadow.SetCoordXRelative(x);
      shadow.SetCoordYRelative(y);
     }
//--- Если стоит флаг перерисовки
   if(redraw)
     {
      //--- полностью перерисовываем объект и запоминаем его новый изначальный вид
      this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c,redraw);
      this.Done();
     }
//--- иначе - стираем объект
   else
      this.Erase();
//--- Перерисуем с флагом перерисовки все прикреплённые объекты
   for(int i=0;i<this.ElementsTotal();i++)
     {
      CWinFormBase *element=this.GetElement(i);
      if(element==NULL)
         continue;
      element.Redraw(redraw);
     }
//--- При установленном флаге перерисовки, и если это главный объект, к которому прикреплены остальные -
//--- перерисуем чарт для немедленного отображения изменений
   if(redraw && this.GetMain()==NULL)
      ::ChartRedraw(this.ChartID());
  }
//+------------------------------------------------------------------+

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

Базовый класс всех WinForms-объектов библиотеки готов.

Теперь доработаем класс объекта-панели в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh.

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

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

Так как теперь все WimForms-объекты будут наследоваться от базового WinForms-объекта, то вместо подключенного файла объекта-формы:

//+------------------------------------------------------------------+
//|                                                        Panel.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\..\Form.mqh"
//+------------------------------------------------------------------+
//| Класс объекта Panel элементов управления WForms                  |
//+------------------------------------------------------------------+
class CPanel : public CForm

подключим файл базового WinForms-объекта:

//+------------------------------------------------------------------+
//|                                                        Panel.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\..\WForms\WinFormBase.mqh"
//+------------------------------------------------------------------+
//| Класс объекта Panel элементов управления WForms                  |
//+------------------------------------------------------------------+


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

class CPanel : public CWinFormBase
  {
private:
   CGCnvElement     *m_obj_top;                                      // Указатель на объект, к координатам которого привязан текущий сверху
   CGCnvElement     *m_obj_bottom;                                   // Указатель на объект, к координатам которого привязан текущий снизу
   CGCnvElement     *m_obj_left;                                     // Указатель на объект, к координатам которого привязан текущий слева
   CGCnvElement     *m_obj_right;                                    // Указатель на объект, к координатам которого привязан текущий справа
   CGCnvElement     *m_underlay;                                     // Подложка для размещения элементов
   bool              m_autoscroll;                                   // Флаг автоматического появления полосы прокрутки
   int               m_autoscroll_margin[2];                         // Массив полей вокруг элемента управления при автоматической прокрутке
   ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode;                 // Режим автоматического изменения размера элемента под содержимое
//--- Создаёт новый графический объект
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string name,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);
//--- Возвращает начальные координаты привязанного объекта
   virtual void      GetCoords(int &x,int &y);
//--- Создаёт объект-подложку
   bool              CreateUnderlayObj(void);
//--- Устанавливает подложку началом отсчёта координат
   void              SetUnderlayAsBase(void);

protected:

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

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

protected:
//--- Возвращает максимальное значение выхода границ Dock-объектов за пределы контейнера по (1) ширине, (2) высоте
   int               GetExcessMaxX(void);
   int               GetExcessMaxY(void);
//--- Устанавливает координату (1) X, (2) Y, (3) ширину, (4) высоту, (5) все параметры подложки
   bool              SetCoordXUnderlay(const int value)              { return(this.m_underlay!=NULL ? this.m_underlay.SetCoordX(value) : false);   }
   bool              SetCoordYUnderlay(const int value)              { return(this.m_underlay!=NULL ? this.m_underlay.SetCoordY(value) : false);   }
   bool              SetWidthUnderlay(const int value)               { return(this.m_underlay!=NULL ? this.m_underlay.SetWidth(value)  : false);   }
   bool              SetHeightUnderlay(const int value)              { return(this.m_underlay!=NULL ? this.m_underlay.SetHeight(value) : false);   }
   bool              SetUnderlayParams(void);
//--- Возвращает координату (1) X, (2) Y, (3) ширину, (4) высоту подложки
   int               GetCoordXUnderlay(void)                   const { return(this.m_underlay!=NULL ?  this.m_underlay.CoordX() : 0);              }
   int               GetCoordYUnderlay(void)                   const { return(this.m_underlay!=NULL ?  this.m_underlay.CoordY() : 0);              }
   int               GetWidthUnderlay(void)                    const { return(this.m_underlay!=NULL ?  this.m_underlay.Width()  : 0);              }
   int               GetHeightUnderlay(void)                   const { return(this.m_underlay!=NULL ?  this.m_underlay.Height() : 0);              }
//--- Возвращает координату (1) X, (2) Y подложки относительно панели
   int               GetCoordXUnderlayRelative(void)           const { return(this.m_underlay!=NULL ?  this.m_underlay.CoordXRelative() : 0);      }
   int               GetCoordYUnderlayRelative(void)           const { return(this.m_underlay!=NULL ?  this.m_underlay.CoordYRelative() : 0);      }
   
//--- Возвращает объект, к координатам которого привязан текущий (1) сверху, (2) снизу, (3) слева, (4) справа
   CGCnvElement     *GetTopObj(void)                                 { return this.m_obj_top;               }
   CGCnvElement     *GetBottomObj(void)                              { return this.m_obj_bottom;            }
   CGCnvElement     *GetLeftObj(void)                                { return this.m_obj_left;              }
   CGCnvElement     *GetRightObj(void)                               { return this.m_obj_right;             }

//--- Подстраивает размеры элемента под его внутреннее содержимое
   bool              AutoSizeProcess(const bool redraw);
   
public:

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

Публичная секция класса, с учётом всех перенесённых методов, внесённых исправлений и добавленных новых методов, теперь выглядит так:

public:
//--- Возвращает (1) подложку, (2) объект, к координатам которого привязан текущий
   CGCnvElement     *GetUnderlay(void)                               { return this.m_underlay;              }
//--- Обновляет координаты (сдвигает канвас)
   virtual bool      Move(const int x,const int y,const bool redraw=false);
//--- Устанавливает координату (1) X, (2) Y, (3) ширину, (4) высоту элемента
   virtual bool      SetCoordX(const int coord_x);
   virtual bool      SetCoordY(const int coord_y);
   virtual bool      SetWidth(const int width);
   virtual bool      SetHeight(const int height);
//--- Создаёт новый присоединённый элемент
   virtual bool      CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                      CGCnvElement *main,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool activity,
                                      const bool redraw);
//--- Перерисовывает объект
   virtual void      Redraw(bool redraw);
//--- Обновляет элемент
   void              Update(const bool redraw=false)                 { this.m_canvas.Update(redraw);        }
   
//--- Сбрасывает размеры всех привязанных объектов к изначальным
   bool              ResetSizeAllToInit(void);
//--- Располагает привязанные объекты в порядке их Dock-привязки
   bool              ArrangeObjects(const bool redraw);

//--- (1) Устанавливает, (2) возвращает флаг автоматического появления полосы прокрутки
   void              SetAutoScroll(const bool flag)                  { this.m_autoscroll=flag;              }
   bool              AutoScroll(void)                                { return this.m_autoscroll;            }
//--- Устанавливает (1) ширину, (2) высоту поля, (3) всех полей вокруг элемента управления при автоматической прокрутке
   void              SetAutoScrollMarginWidth(const int value)       { this.m_autoscroll_margin[0]=value;   }
   void              SetAutoScrollMarginHeight(const int value)      { this.m_autoscroll_margin[1]=value;   }
   void              SetAutoScrollMarginAll(const int value)
                       {
                        this.SetAutoScrollMarginWidth(value); this.SetAutoScrollMarginHeight(value);
                       }
//--- Возвращает (1) ширину, (2) высоту поля вокруг элемента управления при автоматической прокрутке
   int               AutoScrollMarginWidth(void)               const { return this.m_autoscroll_margin[0];  }
   int               AutoScrollMarginHeight(void)              const { return this.m_autoscroll_margin[1];  }
  
//--- (1) Устанавливает флаг автоматического изменения размера элемента под содержимое
   virtual void      SetAutoSize(const bool flag,const bool redraw)
                       {
                        bool prev=this.AutoSize();
                        if(prev==flag)
                           return;
                        CWinFormBase::SetAutoSize(flag,redraw);
                        if(prev!=this.AutoSize() && this.ElementsTotal()>0)
                           this.AutoSizeProcess(redraw);
                       }
//--- (1) Устанавливает, (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.m_autosize_mode=mode;
                        if(prev!=this.AutoSizeMode() && this.ElementsTotal()>0)
                           this.AutoSizeProcess(redraw);
                       }
   ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void)         const { return this.m_autosize_mode;         }
   
//--- (1) Устанавливает, (2) возвращает режим привязки границ элемента к контейнеру
   virtual void      SetDockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode,const bool redraw)
                       {
                        if(this.DockMode()==mode)
                           return;
                        CWinFormBase::SetDockMode(mode,redraw);
                        CPanel *base=this.GetBase();
                        if(base!=NULL)
                           base.ArrangeObjects(redraw);
                       }

//--- Устанавливает промежуток (1) слева, (2) сверху, (3) справа, (4) снизу, (5) со всех сторон внутри элемента управления
   virtual void      SetPaddingLeft(const uint value)
                       {
                        CWinFormBase::SetPaddingLeft(value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Устанавливаем значение смещения подложки по оси X
                           this.m_underlay.SetCoordXRelative(this.PaddingLeft());
                           //--- Устанавливаем координату X и ширину подложки
                           this.SetCoordXUnderlay(this.CoordX()+this.PaddingLeft());
                           this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight());
                          }
                       }
   virtual void      SetPaddingTop(const uint value)
                       {
                        CWinFormBase::SetPaddingTop(value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Устанавливаем значение смещения подложки по оси Y
                           this.m_underlay.SetCoordYRelative(this.PaddingTop());
                           //--- Устанавливаем координату Y и высоту подложки
                           this.SetCoordYUnderlay(this.CoordY()+this.PaddingTop());
                           this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom());
                          }
                       }
   virtual void      SetPaddingRight(const uint value)
                       {
                        CWinFormBase::SetPaddingRight(value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Устанавливаем значение ширины подложки
                           this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight());
                          }
                       }
   virtual void      SetPaddingBottom(const uint value)
                       {
                        CWinFormBase::SetPaddingBottom(value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Устанавливаем значение высоты подложки
                           this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom());
                          }
                       }
   virtual void      SetPaddingAll(const uint value)
                       {
                        this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value);
                       }
   
//--- Устанавливает ширину рамки формы (1) слева, (2) сверху, (3) справа, (4) снизу, (5) всех сторон элемента управления
   virtual void      SetFrameWidthLeft(const uint value)
                       {
                        this.m_frame_width_left=(int)value;
                        if(this.PaddingLeft()<this.FrameWidthLeft())
                           this.SetPaddingLeft(this.FrameWidthLeft());
                       }
   virtual void      SetFrameWidthTop(const uint value)
                       {
                        this.m_frame_width_top=(int)value;
                        if(this.PaddingTop()<this.FrameWidthTop())
                           this.SetPaddingTop(this.FrameWidthTop());
                       }
   virtual void      SetFrameWidthRight(const uint value)
                       {
                        this.m_frame_width_right=(int)value;
                        if(this.PaddingRight()<this.FrameWidthRight())
                           this.SetPaddingRight(this.FrameWidthRight());
                       }
   virtual void      SetFrameWidthBottom(const uint value)
                       {
                        this.m_frame_width_bottom=(int)value;
                        if(this.PaddingBottom()<this.FrameWidthBottom())
                           this.SetPaddingBottom(this.FrameWidthBottom());
                       }
   virtual void      SetFrameWidthAll(const uint value)
                       {
                        this.SetFrameWidthLeft(value); this.SetFrameWidthTop(value); this.SetFrameWidthRight(value); this.SetFrameWidthBottom(value);
                       }

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

Все Set-методы теперь имеют в своём наименовании приставку "Set".

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

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

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

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

В методе, устанавливающем все параметры подложки, укажем тип объекта "подложка":

//+------------------------------------------------------------------+
//| Устанавливает все параметры подложки                             |
//+------------------------------------------------------------------+
bool CPanel::SetUnderlayParams(void)
  {
//--- Устанавливаем тип объекта
   this.m_underlay.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_UNDERLAY);
//--- Устанавливаем в переменные значения смещения подложки по осям X и Y
   this.m_underlay.SetCoordXRelative(this.PaddingLeft());
   this.m_underlay.SetCoordYRelative(this.PaddingTop());
//--- Устанавливаем координаты и размеры подложки
   bool res=true;
   res &=this.SetCoordXUnderlay(this.CoordX()+this.PaddingLeft());
   res &=this.SetCoordYUnderlay(this.CoordY()+this.PaddingTop());
   res &=this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight());
   res &=this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom());
   return res;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Устанавливает координату X панели                                |
//+------------------------------------------------------------------+
bool CPanel::SetCoordX(const int coord_x)
  {
   if(!CGCnvElement::SetCoordX(coord_x))
      return false;
   return(this.m_underlay!=NULL ? this.SetCoordXUnderlay(coord_x+this.PaddingLeft()) : true);
  }
//+------------------------------------------------------------------+
//| Устанавливает координату Y панели                                |
//+------------------------------------------------------------------+
bool CPanel::SetCoordY(const int coord_y)
  {
   if(!CGCnvElement::SetCoordY(coord_y))
      return false;
   return(this.m_underlay!=NULL ? this.SetCoordYUnderlay(coord_y+this.PaddingTop()) : true);
  }
//+------------------------------------------------------------------+
//| Устанавливает ширину панели                                      |
//+------------------------------------------------------------------+
bool CPanel::SetWidth(const int width)
  {
   if(!CGCnvElement::SetWidth(width))
      return false;
   return(this.m_underlay!=NULL ? this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()) : true);
  }
//+------------------------------------------------------------------+
//| Устанавливает высоту панели                                      |
//+------------------------------------------------------------------+
bool CPanel::SetHeight(const int height)
  {
   if(!CGCnvElement::SetHeight(height))
      return false;
   return(this.m_underlay!=NULL ? this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()) : true);
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Сбрасывает размеры всех привязанных объектов к изначальным       |
//+------------------------------------------------------------------+
bool CPanel::ResetSizeAllToInit(void)
  {
   bool res=true;
   for(int i=0;i<this.ElementsTotal();i++)
     {
      CPanel *obj=this.GetElement(i);
      if(obj==NULL)
        {
         res &=false;
         continue;
        }
      res &=obj.Resize(i,obj.GetWidthInit(),obj.GetHeightInit());
     }
   return res;
  }
//+------------------------------------------------------------------+

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


Метод, создающий новый присоединённый элемент:

//+------------------------------------------------------------------+
//| Создаёт новый присоединённый элемент                             |
//+------------------------------------------------------------------+
bool CPanel::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                              CGCnvElement *main,
                              const int x,
                              const int y,
                              const int w,
                              const int h,
                              const color colour,
                              const uchar opacity,
                              const bool activity,
                              const bool redraw)
  {
//--- Если не удалось создать новый графический элемент - возвращаем false
   CGCnvElement *obj=CForm::CreateAndAddNewElement(element_type,main,x,y,w,h,colour,opacity,activity);
   if(obj==NULL)
      return false;
//--- Если тип объекта - базовый WinForms-объект и выше
   if(obj.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE)
     {
      //--- объявляем указатель с типом базового WinForms-объекта и присваиваем ему указатель на созданный объект,
      //--- устанавливаем цвет рамки равным цвету фона 
      CWinFormBase *wf=obj;
      wf.SetColorFrame(wf.ColorBackground());
     }
//--- Если у панели включено автоизменение размеров и есть привязанные объекты - вызываем метод изменения размеров
   if(this.AutoSize() && this.ElementsTotal()>0)
      this.AutoSizeProcess(redraw);
//--- Перерисовываем панель и все добавленные объекты и возвращаем true
   this.Redraw(redraw);
   return true;
  }
//+------------------------------------------------------------------+

Логика метода расписана в комментариях к коду и достаточно проста.

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

//+------------------------------------------------------------------+
//| Возвращает максимальное значение выхода границ Dock-объектов     |
//| за пределы контейнера по ширине                                  |
//+------------------------------------------------------------------+
int CPanel::GetExcessMaxX(void)
  {
   int value=0;
   for(int i=0;i<this.ElementsTotal();i++)
     {
      CWinFormBase *element=this.GetElement(i);
      if(element==NULL)
         continue;
      if(element.RightEdge()>value)
         value=element.RightEdge();
     }
   return value-this.m_underlay.RightEdge();
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Возвращает максимальное значение выхода границ Dock-объектов     |
//| за пределы контейнера по высоте                                  |
//+------------------------------------------------------------------+
int CPanel::GetExcessMaxY(void)
  {
   int value=0;
   for(int i=0;i<this.ElementsTotal();i++)
     {
      CWinFormBase *element=this.GetElement(i);
      if(element==NULL)
         continue;
      if(element.BottomEdge()>value)
         value=element.BottomEdge();
     }
   return value-this.m_underlay.BottomEdge();
  }
//+------------------------------------------------------------------+

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


Метод, располагающий привязанные объекты в порядке их Dock-привязки:

//+------------------------------------------------------------------+
//| Располагает привязанные объекты в порядке их Dock-привязки       |
//+------------------------------------------------------------------+
bool CPanel::ArrangeObjects(const bool redraw)
  {
   CWinFormBase *prev=NULL, *obj=NULL;
   //--- Если включен режим автоизменения размеров
   if(this.AutoSize())
     {
      //--- В цикле во всем привязанным объектам
      for(int i=0;i<this.ElementsTotal();i++)
        {
         //--- Получим предыдущий элемент из списка
         prev=this.GetElement(i-1);
         //--- Если предыдущего элемента нет - устанавливаем подложку как предыдущий элемент
         if(prev==NULL)
            this.SetUnderlayAsBase();
         //--- Получаем текущий элемент
         obj=GetElement(i);
         //--- Если объект не получен, или его тип меньше базового WinForms-объекта, или у него нет подложки - идём далее
         if(obj==NULL || obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || this.m_underlay==NULL)
            continue;
         //--- В зависимости от режима привязки текущего объекта ...
         //--- Привязка сверху
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP)
           {
            
           }
         //--- Привязка снизу
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM)
           {
            
           }
         //--- Привязка слева
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT)
           {
            
           }
         //--- Привязка справа
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT)
           {
            
           }
         //--- Привязка с заливкой
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL)
           {
            
           }
         //--- Нет привязки
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_NONE)
           {
            
           }
        }
      this.Resize(this.GetWidthInit(),this.GetHeightInit(),false);
     }
   //--- Если режим автоизменения размеров отключен 
   else
     {
      //--- В цикле во всем привязанным объектам
      for(int i=0;i<this.ElementsTotal();i++)
        {
         //--- Получаем текущий и предыдущий элементы из списка
         obj=this.GetElement(i);
         prev=this.GetElement(i-1);
         //--- Если предыдущего элемента нет - устанавливаем подложку как предыдущий элемент
         if(prev==NULL)
            this.SetUnderlayAsBase();
         //--- Если объект не получен, или его тип меньше базового WinForms-объекта, или у него нет подложки - идём далее
         if(obj==NULL || obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || this.m_underlay==NULL)
            continue;
         int x=0, y=0; // Координаты привязки объекта
         //--- В зависимости от режима привязки текущего объекта ...
         //--- Привязка сверху
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP)
           {
            //--- Если не удалось изменить размеры объекта (на всю ширину подложки и на изначальную высоту объекта) - идём к следующему
            if(!obj.Resize(this.GetWidthUnderlay(),obj.GetHeightInit(),false))
               continue;
            //--- Получаем указатель на объект сверху, к граям которого нужно прикрепить текущий
            CGCnvElement *coord_base=this.GetTopObj();
            //--- Получаем координаты привязки объекта
            x=this.GetCoordXUnderlay();
            y=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.CoordY() : coord_base.BottomEdge()+1);
            //--- Если не удалось сместить объект на полученные координаты - идём к следующему
            if(!obj.Move(x,y,false))
               continue;
            //--- Устанавливаем текущий объект как верхний объект, к граням которого будет привязан следующий
            this.m_obj_top=obj;
           }
         //--- Привязка снизу
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM)
           {
            //--- Если не удалось изменить размеры объекта (на всю ширину подложки и на изначальную высоту объекта) - идём к следующему
            if(!obj.Resize(this.GetWidthUnderlay(),obj.GetHeightInit(),false))
               continue;
            //--- Получаем указатель на объект снизу, к граям которого нужно прикрепить текущий
            CGCnvElement *coord_base=this.GetBottomObj();
            //--- Получаем координаты привязки объекта
            x=this.GetCoordXUnderlay();
            y=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.BottomEdge()-obj.Height()-1 : coord_base.CoordY()-obj.Height()-1);
            //--- Если не удалось сместить объект на полученные координаты - идём к следующему
            if(!obj.Move(x,y,false))
               continue;
            //--- Устанавливаем текущий объект как нижний объект, к граням которого будет привязан следующий
            this.m_obj_bottom=obj;
           }
         //--- Привязка слева
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT)
           {
            //--- Если не удалось изменить размеры объекта (на изначальную ширину объекта и на всю высоту подложки) - идём к следующему
            if(!obj.Resize(obj.GetWidthInit(),this.GetHeightUnderlay(),false))
               continue;
            //--- Получаем указатель на объект слева, к граям которого нужно прикрепить текущий
            CGCnvElement *coord_base=this.GetLeftObj();
            //--- Получаем координаты привязки объекта
            x=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.CoordX() : coord_base.RightEdge()+1);
            y=this.GetCoordYUnderlay();
            //--- Если не удалось сместить объект на полученные координаты - идём к следующему
            if(!obj.Move(x,y,false))
               continue;
            //--- Устанавливаем текущий объект как объект слева, к граням которого будет привязан следующий
            this.m_obj_left=obj;
           }
         //--- Привязка справа
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT)
           {
            //--- Если не удалось изменить размеры объекта (на изначальную ширину объекта и на всю высоту подложки) - идём к следующему
            if(!obj.Resize(obj.GetWidthInit(),this.GetHeightUnderlay(),false))
               continue;
            //--- Получаем указатель на объект справа, к граям которого нужно прикрепить текущий
            CGCnvElement *coord_base=this.GetRightObj();
            //--- Получаем координаты привязки объекта
            x=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? m_underlay.RightEdge()-obj.Width() : coord_base.CoordX()-obj.Width()-1);
            y=this.GetCoordYUnderlay();
            //--- Если не удалось сместить объект на полученные координаты - идём к следующему
            if(!obj.Move(x,y,false))
               continue;
            //--- Устанавливаем текущий объект как объект справа, к граням которого будет привязан следующий
            this.m_obj_right=obj;
           }
         //--- Привязка с заливкой
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL)
           {
            //--- Если не удалось изменить размеры объекта (на всю ширину и высоту подложки) - идём к следующему
            if(!obj.Resize(this.GetWidthUnderlay(),this.GetHeightUnderlay(),false))
               continue;
            //--- Устанавливаем подложку как объект привязки
            this.SetUnderlayAsBase();
            //--- Получаем координаты привязки объекта
            x=this.GetLeftObj().CoordX();
            y=this.GetTopObj().CoordY();
            //--- Если не удалось сместить объект на полученные координаты - идём к следующему
            if(!obj.Move(x,y,false))
               continue;
           }
         //--- Нет привязки
         if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_NONE)
           {
            //--- Изменяем размеры объекта на изначальные
            obj.Resize(obj.GetWidthInit(),obj.GetHeightInit(),false);
            //--- Получаем изначальныекоординаты расположения объекта
            x=this.GetCoordXUnderlay()+obj.CoordXRelativeInit();
            y=this.GetCoordYUnderlay()+obj.CoordYRelativeInit();
            //--- Если не удалось сместить объект на полученные координаты - идём к следующему
            if(!obj.Move(x,y,false))
               continue;
           }
         //--- Рассчитываем и устанавливаем относительные координаты объекта
         obj.SetCoordXRelative(x-this.m_underlay.CoordX());
         obj.SetCoordYRelative(y-this.m_underlay.CoordY());
        }
     }
//--- Перерисуем объект с флагом перерисовки и вернём true
   this.Redraw(redraw); 
   return true;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Устанавливает подложку началом отсчёта координат                 |
//+------------------------------------------------------------------+
void CPanel::SetUnderlayAsBase(void)
  {
   this.m_obj_left=this.m_underlay;
   this.m_obj_right=this.m_underlay;
   this.m_obj_top=this.m_underlay;
   this.m_obj_bottom=this.m_underlay;
  }
//+------------------------------------------------------------------+

Здесь просто всем четырём объектам привязки присваиваем указатель на подложку.

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

//+------------------------------------------------------------------+
//| Подстраивает размеры элемента под его внутреннее содержимое      |
//+------------------------------------------------------------------+
bool CPanel::AutoSizeProcess(const bool redraw)
  {
//--- Получаем значения по X и Y, на которые необходимо изменить размеры панели
   int excess_w=this.GetExcessMaxX();
   int excess_h=this.GetExcessMaxY();
//--- Если размеры менять не нужно - возвращаем true
   if(excess_w==0 && excess_h==0)
      return true;
   //--- Если нужно изменить размеры панели - возвращаем результат их корректировки
   return
     (
      //--- если только увеличение размеров
      this.AutoSizeMode()==CANV_ELEMENT_AUTO_SIZE_MODE_GROW ? 
      this.Resize(this.Width()+(excess_w>0  ? excess_w : 0),this.Height()+(excess_h>0  ? excess_h : 0),redraw) :
      //--- если увеличение и уменьшение размеров
      this.Resize(this.Width()+(excess_w!=0 ? excess_w : 0),this.Height()+(excess_h!=0 ? excess_h : 0),redraw)
     );
  }
//+------------------------------------------------------------------+

Логика метода расписана в комментариях к коду. Вкратце: получаем максимальные величины по X и Y, на которые привязанные объекты выходит за пределы подложки. Положительная величина указывает на то, что объекты выходят за пределы подложки, отрицательная — на то, что подложка слишком большая и её можно уменьшить. Ну и возвращаем результат изменения размеров панели с учётом режима автоизменения её размеров (либо только увеличение, либо увеличение и уменьшение)


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

В файле \MQL5\Include\DoEasy\Engine.mqh в методах, возвращающих объект WForm Panel, нужно поменять старое наименование макроподстановки GRAPH_ELEMENT_TYPE_PANEL на новое: GRAPH_ELEMENT_TYPE_WF_PANEL:

//--- Возвращает объект WForm Element по идентификатору объекта
   CForm               *GetWFForm(const int element_id)
                          {
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_FORM);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
   
//--- Возвращает объект WForm Panel по имени объекта на текущем графике
   CPanel              *GetWFPanel(const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Возвращает объект WForm Panel по идентификатору графика и имени объекта
   CPanel              *GetWFPanel(const long chart_id,const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Возвращает объект WForm Panel по идентификатору объекта
   CPanel              *GetWFPanel(const int element_id)
                          {
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }

//--- Создаёт объект WinForm Element

На сегодня это все необходимые изменения и доработки.


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

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

Что и как будем тестировать... В настройки советника добавим флаг автоизменения размера панели под её внутреннее содержимое и режим автоматического изменения размера.

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

//+------------------------------------------------------------------+
//| Границы элемента управления, привязанные к контейнеру            |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_DOCK_MODE
  {
   CANV_ELEMENT_DOCK_MODE_NONE,                       // Прикреплён к указанным координатам, размеры не меняются
   CANV_ELEMENT_DOCK_MODE_TOP,                        // Присоединение сверху и растягивание на ширину контейнера
   CANV_ELEMENT_DOCK_MODE_BOTTOM,                     // Присоединение снизу и растягивание на ширину контейнера
   CANV_ELEMENT_DOCK_MODE_LEFT,                       // Присоединение слева и растягивание на высоту контейнера
   CANV_ELEMENT_DOCK_MODE_RIGHT,                      // Присоединение справа и растягивание на высоту контейнера
   CANV_ELEMENT_DOCK_MODE_FILL,                       // Растягивание на ширину и высоту всего контейнера
  };
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//|                                            TestDoEasyPart105.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- defines
#define  FORMS_TOTAL (3)   // Количество создаваемых форм
#define  START_X     (4)   // Начальная координата X фигуры
#define  START_Y     (4)   // Начальная координата Y фигуры
#define  KEY_LEFT    (65)  // (A) Влево
#define  KEY_RIGHT   (68)  // (D) Вправо
#define  KEY_UP      (87)  // (W) Вверх
#define  KEY_DOWN    (88)  // (X) Вниз
#define  KEY_FILL    (83)  // (S) Заполнение
#define  KEY_ORIGIN  (90)  // (Z) По умолчанию
#define  KEY_INDEX   (81)  // (Q) По индексу

//--- enumerations by compilation language
#ifdef COMPILE_EN
enum ENUM_AUTO_SIZE_MODE
  {
   AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW,                // Grow
   AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK   // Grow and Shrink
  };
#else 
enum ENUM_AUTO_SIZE_MODE
  {
   AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW,                // Только увеличение
   AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK   // Увеличение и уменьшение
  };
#endif 
//--- input parameters
sinput   bool                 InpMovable        =  true;                // Movable forms flag
sinput   ENUM_INPUT_YES_NO    InpAutoSize       =  INPUT_YES;           // Autosize
sinput   ENUM_AUTO_SIZE_MODE  InpAutoSizeMode   =  AUTO_SIZE_MODE_GROW; // Autosize Mode
//--- global variables
CEngine        engine;
color          array_clr[];
//+------------------------------------------------------------------+

Из обработчика OnInit() удалим код для создания форм и элементов. Оставим лишь создание одной панели и в цикле в самой панели создадим шесть привязанных объектов-панелей с флагом перерисовки false. При этом высота панели будет немного меньше, чем общая высота всех построенных внутри неё объектов, а ширина, наоборот — больше. Таким образом мы сможем посмотреть, как панель подстраивается под размеры построенных внутри неё объектов:

//+------------------------------------------------------------------+
//| 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;
   pnl=engine.CreateWFPanel("WFPanel",50,50,230,150,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true);
   if(pnl!=NULL)
     {
      //--- Установим значение Padding равным 4
      pnl.SetPaddingAll(4);
      //--- Установим флаги перемещаемости, автоизменения размеров и режим автоизменения из входных параметров
      pnl.SetMovable(InpMovable);
      pnl.SetAutoSize(InpAutoSize,false);
      pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false);
      //--- В цикле создадим 6 привязанных объектов-панелей
      for(int i=0;i<6;i++)
        {
         //--- создадим объект-панель с координатами по оси X по центру, и 10 по оси Y, шириной 80 и высотой 30
         CPanel *prev=pnl.GetElement(i-1);
         int xb=0, yb=0;
         int x=(i<3 ? (prev==NULL ? xb : prev.CoordXRelative()) : xb+prev.Width()+20);
         int y=(i<3 ? (prev==NULL ? yb : prev.BottomEdgeRelative()+16) : (i==3 ? yb : prev.BottomEdgeRelative()+16));
         pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,pnl,x,y,80,40,C'0xCD,0xDA,0xD7',200,true,false);
        }
      pnl.Redraw(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

После создания всех элементов внутри панели, полностью перерисуем всю панель вместе с созданными в ней элементами (флаг перерисовки true)

В обработчике событий OnChartEvent() в блоке обработки нажатий клавиш впишем обработчик нажатия клавиши Q на клавиатуре:

   //--- Если нажата клавиша на клавиатуре
   if(id==CHARTEVENT_KEYDOWN)
     {
      CPanel *panel=engine.GetWFPanel(0);
      if(panel!=NULL && (lparam==KEY_UP || lparam==KEY_DOWN || lparam==KEY_LEFT || lparam==KEY_RIGHT || lparam==KEY_FILL || lparam==KEY_ORIGIN || lparam==KEY_INDEX))
        {
         for(int i=0;i<panel.ElementsTotal();i++)
           {
            CPanel *obj=panel.GetElement(i);
            if(obj!=NULL)
              {
               if(lparam==KEY_UP)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_TOP,false);
               else if(lparam==KEY_DOWN)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_BOTTOM,false);
               else if(lparam==KEY_LEFT)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_LEFT,false);
               else if(lparam==KEY_RIGHT)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_RIGHT,false);
               else if(lparam==KEY_FILL)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_FILL,false);
               else if(lparam==KEY_ORIGIN)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
               else if(lparam==KEY_INDEX)
                 {
                  obj.SetDockMode((ENUM_CANV_ELEMENT_DOCK_MODE)i,true);
                  Sleep(i>0 ? 500 : 0);
                 }
              }
           }
         panel.Redraw(true);
        }
     }

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

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


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

Что дальше

В следующей статье продолжим работу над WinForms-объектами.

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

К содержанию

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

DoEasy. Элементы управления (Часть 1): Первые шаги
DoEasy. Элементы управления (Часть 2): Продолжаем работу над классом CPanel
DoEasy. Элементы управления (Часть 3): Создание привязанных элементов управления
DoEasy. Элементы управления (Часть 4): Элемент управления "Панель", параметры Padding и Dock


Прикрепленные файлы |
MQL5.zip (4422.79 KB)
Нейросети — это просто (Часть 15): Кластеризации данных средствами MQL5 Нейросети — это просто (Часть 15): Кластеризации данных средствами MQL5
Продолжаем рассмотрение метода кластеризации. В данной статье мы создадим новый класс CKmeans для реализации одного из наиболее распространённых методов кластеризации k-средних. По результатам тестирования модель смогла выделить около 500 паттернов.
Разработка торгового советника с нуля (Часть 7): Добавляем Volume At Price (I) Разработка торгового советника с нуля (Часть 7): Добавляем Volume At Price (I)
Это один из самых мощных индикаторов из существующих. Те, кто торгует и старается иметь определенную степень уверенности, не могут не иметь этот индикатор на своем графике. Хотя чаще всего его используют те, кто торгует, наблюдая за лентой сделок («tape reading»). Также этот индикатор могут использовать и те, кто использует только Price Action.
Машинное обучение и Data Science (Часть 01): Линейная регрессия Машинное обучение и Data Science (Часть 01): Линейная регрессия
Пришло время нам, трейдерам, обучить наши системы и научиться самим принимать решения, основываясь на том, что показывают цифры. Не визуальным и не интуитивным путем, которым движется весь мир. Мы пойдем перпендикулярно общему направлению.
Нейросети — это просто (Часть 14): Кластеризация данных Нейросети — это просто (Часть 14): Кластеризация данных
Должен признаться, что с момента публикации последней статьи прошло уже больше года. За столь длительное время можно многое переосмыслить, выработать новые подходы. И в новой статье я хотел бы немного отойти от используемого ранее метода обучения с учителем, и предложить немного окунуться в алгоритмы обучения без учителя. И, в частности, рассмотреть один из алгоритмов кластеризации — k-средних.