DoEasy. Элементы управления (Часть 6): Элемент управления "Панель", автоизменение размеров контейнера под внутреннее содержимое

27 мая 2022, 14:04
Artyom Trishkin
0
432

Содержание


Концепция

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

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

Помимо работы над WinForms-объектами, добавим к объекту библиотеки "Символ" новые свойства, ранее анонсированные для MetaTrader 5, Build 3260:

MQL5: Добавлены новые свойства в перечисление ENUM_SYMBOL_INFO_DOUBLE:

  • SYMBOL_SWAP_SUNDAY
  • SYMBOL_SWAP_MONDAY
  • SYMBOL_SWAP_TUESDAY
  • SYMBOL_SWAP_WEDNESDAY
  • SYMBOL_SWAP_THURSDAY
  • SYMBOL_SWAP_FRIDAY
  • SYMBOL_SWAP_SATURDAY

Они позволяют получить коэффициент начисления свопов для каждого дня недели. 1 — одиночное начисление свопов, 3 — тройное, 0 — начисление отсутствует.

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


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

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

   MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3245,          // Свойство не поддерживается в MetaTrader5 версии ниже 3245
   MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3260,          // Свойство не поддерживается в MetaTrader5 версии ниже 3260
   MSG_LIB_PROP_NOT_SUPPORTED_POSITION,               // Свойство не поддерживается у позиции

...

   MSG_SYM_PROP_SUBSCRIPTION_DELAY,                   // Размер задержки у котировок, передаваемых по символу, для инструментов, работающих по подписке
   MSG_SYM_PROP_SWAP_SUNDAY,                          // Коэффициент начисления свопов. Воскресение
   MSG_SYM_PROP_SWAP_MONDAY,                          // Коэффициент начисления свопов. Понедельник
   MSG_SYM_PROP_SWAP_TUESDAY,                         // Коэффициент начисления свопов. Вторник
   MSG_SYM_PROP_SWAP_WEDNESDAY,                       // Коэффициент начисления свопов. Среда
   MSG_SYM_PROP_SWAP_THURSDAY,                        // Коэффициент начисления свопов. Четверг
   MSG_SYM_PROP_SWAP_FRIDAY,                          // Коэффициент начисления свопов. Пятница
   MSG_SYM_PROP_SWAP_SATURDAY,                        // Коэффициент начисления свопов. Суббота
   MSG_SYM_PROP_SWAP_1,                               // Одиночное начисление свопов
   MSG_SYM_PROP_SWAP_3,                               // Тройное начисление свопов
   MSG_SYM_PROP_SWAP_0,                               // Начисление свопов отсутствует
   //---
   MSG_SYM_PROP_BIDHIGH,                              // Максимальный Bid за день

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

   {"Свойство не поддерживается в MetaTrader5 версии ниже 3245","The property is not supported in MetaTrader5, build lower than 3245"},
   {"Свойство не поддерживается в MetaTrader5 версии ниже 3260","The property is not supported in MetaTrader5, build lower than 3260"},
   {"Свойство не поддерживается у позиции","Property not supported for position"},

...

   {"Размер задержки у котировок, передаваемых по символу, для инструментов, работающих по подписке","Delay size for quotes transmitted per symbol for instruments working by subscription"},
   {"Коэффициент начисления свопов. Воскресение","Swap rate. Sunday"},
   {"Коэффициент начисления свопов. Понедельник","Swap rate. Monday"},
   {"Коэффициент начисления свопов. Вторник","Swap rate. Tuesday"},
   {"Коэффициент начисления свопов. Среда","Swap rate. Wednesday"},
   {"Коэффициент начисления свопов. Четверг","Swap rate. Thursday"},
   {"Коэффициент начисления свопов. Пятница","Swap rate. Friday"},
   {"Коэффициент начисления свопов. Суббота","Swap rate. Saturday"},
   {"Одиночное начисление свопов","Single swap accrual"},
   {"Тройное начисление свопов","Triple swap accrual"},
   {"Начисление свопов отсутствует","No accrual"},
   
   {"Максимальный Bid за день","Maximal Bid of the day"},


В файле \MQL5\Include\DoEasy\Defines.mqh в перечисление вещественных свойств объекта-символа впишем новые свойства и увеличим количество вещественных свойств символа с 68 до 75:

   SYMBOL_PROP_PRICE_SENSITIVITY,                           // Чувствительность опциона/варранта.  Показывает, на сколько пунктов должна измениться цена базового актива опциона, чтобы цена опциона изменилась на один пункт
   SYMBOL_PROP_SWAP_SUNDAY,                                 // Коэффициент начисления свопов для воскресения
   SYMBOL_PROP_SWAP_MONDAY,                                 // Коэффициент начисления свопов для понедельника
   SYMBOL_PROP_SWAP_TUESDAY,                                // Коэффициент начисления свопов для вторника
   SYMBOL_PROP_SWAP_WEDNESDAY,                              // Коэффициент начисления свопов для среды
   SYMBOL_PROP_SWAP_THURSDAY,                               // Коэффициент начисления свопов для четверга
   SYMBOL_PROP_SWAP_FRIDAY,                                 // Коэффициент начисления свопов для пятницы
   SYMBOL_PROP_SWAP_SATURDAY,                               // Коэффициент начисления свопов для субботы
  };
#define SYMBOL_PROP_DOUBLE_TOTAL     (75)                   // Общее количество вещественных свойств
#define SYMBOL_PROP_DOUBLE_SKIP      (0)                    // Количество неиспользуемых в сортировке вещественных свойств символа
//+------------------------------------------------------------------+

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

   SORT_BY_SYMBOL_PRICE_SENSITIVITY,                        // Сортировать по чувствительности опциона/варранта
   SORT_BY_SYMBOL_SWAP_SUNDAY,                              // Сортировать по коэффициенту начисления свопов для воскресения
   SORT_BY_SYMBOL_SWAP_MONDAY,                              // Сортировать по коэффициенту начисления свопов для понедельника
   SORT_BY_SYMBOL_SWAP_TUESDAY,                             // Сортировать по коэффициенту начисления свопов для вторника
   SORT_BY_SYMBOL_SWAP_WEDNESDAY,                           // Сортировать по коэффициенту начисления свопов для среды
   SORT_BY_SYMBOL_SWAP_THURSDAY,                            // Сортировать по коэффициенту начисления свопов для четверга
   SORT_BY_SYMBOL_SWAP_FRIDAY,                              // Сортировать по коэффициенту начисления свопов для пятницы
   SORT_BY_SYMBOL_SWAP_SATURDAY,                            // Сортировать по коэффициенту начисления свопов для субботы
//--- Сортировка по строковым свойствам
   SORT_BY_SYMBOL_NAME = FIRST_SYM_STR_PROP,                // Сортировать по имени символа


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

   CANV_ELEMENT_PROP_ENABLED,                         // Флаг доступности элемента
   CANV_ELEMENT_PROP_FORE_COLOR,                      // Цвет текста по умолчанию для всех объектов элемента управления
   CANV_ELEMENT_PROP_BOLD_TYPE,                       // Тип толщины шрифта
   CANV_ELEMENT_PROP_BORDER_STYLE,                    // Стиль рамки элемента управления
   CANV_ELEMENT_PROP_AUTOSIZE,                        // Флаг автоматического изменения размера элемента управления под содержимое
   CANV_ELEMENT_PROP_DOCK_MODE,                       // Режим привязки границ элемента управления к контейнеру
   CANV_ELEMENT_PROP_MARGIN_TOP,                      // Промежуток сверху между полями данного и другого элемента управления
   CANV_ELEMENT_PROP_MARGIN_BOTTOM,                   // Промежуток снизу между полями данного и другого элемента управления
   CANV_ELEMENT_PROP_MARGIN_LEFT,                     // Промежуток слева между полями данного и другого элемента управления
   CANV_ELEMENT_PROP_MARGIN_RIGHT,                    // Промежуток справа между полями данного и другого элемента управления
   CANV_ELEMENT_PROP_PADDING_TOP,                     // Промежуток сверху внутри элемента управления
   CANV_ELEMENT_PROP_PADDING_BOTTOM,                  // Промежуток снизу внутри элемента управления
   CANV_ELEMENT_PROP_PADDING_LEFT,                    // Промежуток слева внутри элемента управления
   CANV_ELEMENT_PROP_PADDING_RIGHT,                   // Промежуток справа внутри элемента управления
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (38)          // Общее количество целочисленных свойств
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Количество неиспользуемых в сортировке целочисленных свойств

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

   SORT_BY_CANV_ELEMENT_ENABLED,                      // Сортировать по флагу доступности элемента
   SORT_BY_CANV_ELEMENT_FORE_COLOR,                   // Сортировать по цвету текста по умолчанию для всех объектов элемента управления
   SORT_BY_CANV_ELEMENT_BOLD_TYPE,                    // Сортировать по типу толщины шрифта
   SORT_BY_CANV_ELEMENT_BORDER_STYLE,                 // Сортировать по стилю рамки элемента управления
   SORT_BY_CANV_ELEMENT_AUTOSIZE,                     // Сортировать по флагу автоматического изменения размера элемента управления под содержимое
   SORT_BY_CANV_ELEMENT_DOCK_MODE,                    // Сортировать по режиму привязки границ элемента управления к контейнеру
   SORT_BY_CANV_ELEMENT_MARGIN_TOP,                   // Сортировать по промежутку сверху между полями данного и другого элемента управления
   SORT_BY_CANV_ELEMENT_MARGIN_BOTTOM,                // Сортировать по промежутку снизу между полями данного и другого элемента управления
   SORT_BY_CANV_ELEMENT_MARGIN_LEFT,                  // Сортировать по промежутку слева между полями данного и другого элемента управления
   SORT_BY_CANV_ELEMENT_MARGIN_RIGHT,                 // Сортировать по промежутку справа между полями данного и другого элемента управления
   SORT_BY_CANV_ELEMENT_PADDING_TOP,                  // Сортировать по промежутку сверху внутри элемента управления
   SORT_BY_CANV_ELEMENT_PADDING_BOTTOM,               // Сортировать по промежутку снизу внутри элемента управления
   SORT_BY_CANV_ELEMENT_PADDING_LEFT,                 // Сортировать по промежутку слева внутри элемента управления
   SORT_BY_CANV_ELEMENT_PADDING_RIGHT,                // Сортировать по промежутку справа внутри элемента управления
//--- Сортировка по вещественным свойствам

//--- Сортировка по строковым свойствам
   SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Сортировать по имени объекта-элемента
   SORT_BY_CANV_ELEMENT_NAME_RES,                     // Сортировать по имени графического ресурса
  };
//+------------------------------------------------------------------+

Теперь все WinForms-объекты могут быть выбраны и отсортированы по тем свойствам графических объектов, которые присущи только WinForms-объектам.


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

В файле класса \MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh внесём необходимые доработки.

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

   double            SymbolMarginHedged(void)            const;
   double            SymbolSwapRatio(ENUM_DAY_OF_WEEK day)const;
   bool              SymbolMarginLong(void);


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

   string            GetSectorDescription(void)                   const;
   string            GetIndustryDescription(void)                 const;
   string            GetSwapRatioDescription(const ENUM_DAY_OF_WEEK day)const;


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

   double            PriceSensitivity(void)                       const { return this.GetProperty(SYMBOL_PROP_PRICE_SENSITIVITY);                           }
   double            SwapRatioSunday(void)                        const { return this.GetProperty(SYMBOL_PROP_SWAP_SUNDAY);                                 }
   double            SwapRatioMonday(void)                        const { return this.GetProperty(SYMBOL_PROP_SWAP_MONDAY);                                 }
   double            SwapRatioTuesday(void)                       const { return this.GetProperty(SYMBOL_PROP_SWAP_TUESDAY);                                }
   double            SwapRatioWednesday(void)                     const { return this.GetProperty(SYMBOL_PROP_SWAP_WEDNESDAY);                              }
   double            SwapRatioThursday(void)                      const { return this.GetProperty(SYMBOL_PROP_SWAP_THURSDAY);                               }
   double            SwapRatioFriday(void)                        const { return this.GetProperty(SYMBOL_PROP_SWAP_FRIDAY);                                 }
   double            SwapRatioSaturday(void)                      const { return this.GetProperty(SYMBOL_PROP_SWAP_SATURDAY);                               }
   double            SwapRatioDay(const ENUM_DAY_OF_WEEK day)     const;
   double            NormalizedPrice(const double price)          const;


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

   //--- Чувствительность опциона/варранта
   //--- установка контролируемой величины (1) прироста, (2) уменьшения, (3) контрольного уровня чувствительности опциона/варранта
   //--- получение (4) величины изменения чувствительности опциона/варранта,
   //--- получение флага изменения чувствительности опциона/варранта больше, чем на величину (5) прироста, (6) уменьшения
   void              SetControlPriceSensitivityInc(const double value)        { this.SetControlledValueINC(SYMBOL_PROP_PRICE_SENSITIVITY,::fabs(value));             }
   void              SetControlPriceSensitivityDec(const double value)        { this.SetControlledValueDEC(SYMBOL_PROP_PRICE_SENSITIVITY,::fabs(value));             }
   void              SetControlPriceSensitivityLevel(const double value)      { this.SetControlledValueLEVEL(SYMBOL_PROP_PRICE_SENSITIVITY,::fabs(value));           }
   double            GetValueChangedPriceSensitivity(void)              const { return this.GetPropDoubleChangedValue(SYMBOL_PROP_PRICE_SENSITIVITY);                }
   bool              IsIncreasedPriceSensitivity(void)                  const { return (bool)this.GetPropDoubleFlagINC(SYMBOL_PROP_PRICE_SENSITIVITY);               }
   bool              IsDecreasedPriceSensitivity(void)                  const { return (bool)this.GetPropDoubleFlagDEC(SYMBOL_PROP_PRICE_SENSITIVITY);               }
//--- Коэффициент начисления свопов
   //--- установка контролируемой величины (1) прироста, (2) уменьшения, (3) контрольного уровня коэффициента начисления свопов для воскресения
   //--- получение (4) величины изменения коэффициента начисления свопов для воскресения,
   //--- получение флага изменения коэффициента начисления свопов для воскресения больше, чем на величину (5) прироста, (6) уменьшения
   void              SetControlSwapSundayInc(const double value)              { this.SetControlledValueINC(SYMBOL_PROP_SWAP_SUNDAY,::fabs(value));                   }
   void              SetControlSwapSundayDec(const double value)              { this.SetControlledValueDEC(SYMBOL_PROP_SWAP_SUNDAY,::fabs(value));                   }
   void              SetControlSwapSundayLevel(const double value)            { this.SetControlledValueLEVEL(SYMBOL_PROP_SWAP_SUNDAY,::fabs(value));                 }
   double            GetValueChangedSwapSunday(void)                    const { return this.GetPropDoubleChangedValue(SYMBOL_PROP_SWAP_SUNDAY);                      }
   bool              IsIncreasedSwapSunday(void)                        const { return (bool)this.GetPropDoubleFlagINC(SYMBOL_PROP_SWAP_SUNDAY);                     }
   bool              IsDecreasedSwapSunday(void)                        const { return (bool)this.GetPropDoubleFlagDEC(SYMBOL_PROP_SWAP_SUNDAY);                     }
   //--- установка контролируемой величины (1) прироста, (2) уменьшения, (3) контрольного уровня коэффициента начисления свопов для понедельника
   //--- получение (4) величины изменения коэффициента начисления свопов для понедельника,
   //--- получение флага изменения коэффициента начисления свопов для понедельника больше, чем на величину (5) прироста, (6) уменьшения
   void              SetControlSwapMondayInc(const double value)              { this.SetControlledValueINC(SYMBOL_PROP_SWAP_MONDAY,::fabs(value));                   }
   void              SetControlSwapMondayDec(const double value)              { this.SetControlledValueDEC(SYMBOL_PROP_SWAP_MONDAY,::fabs(value));                   }
   void              SetControlSwapMondayLevel(const double value)            { this.SetControlledValueLEVEL(SYMBOL_PROP_SWAP_MONDAY,::fabs(value));                 }
   double            GetValueChangedSwapMonday(void)                    const { return this.GetPropDoubleChangedValue(SYMBOL_PROP_SWAP_MONDAY);                      }
   bool              IsIncreasedSwapMonday(void)                        const { return (bool)this.GetPropDoubleFlagINC(SYMBOL_PROP_SWAP_MONDAY);                     }
   bool              IsDecreasedSwapMonday(void)                        const { return (bool)this.GetPropDoubleFlagDEC(SYMBOL_PROP_SWAP_MONDAY);                     }
   //--- установка контролируемой величины (1) прироста, (2) уменьшения, (3) контрольного уровня коэффициента начисления свопов для вторника
   //--- получение (4) величины изменения коэффициента начисления свопов для вторника,
   //--- получение флага изменения коэффициента начисления свопов для вторника больше, чем на величину (5) прироста, (6) уменьшения
   void              SetControlSwapTuesdayInc(const double value)             { this.SetControlledValueINC(SYMBOL_PROP_SWAP_TUESDAY,::fabs(value));                  }
   void              SetControlSwapTuesdayDec(const double value)             { this.SetControlledValueDEC(SYMBOL_PROP_SWAP_TUESDAY,::fabs(value));                  }
   void              SetControlSwapTuesdayLevel(const double value)           { this.SetControlledValueLEVEL(SYMBOL_PROP_SWAP_TUESDAY,::fabs(value));                }
   double            GetValueChangedSwapTuesday(void)                   const { return this.GetPropDoubleChangedValue(SYMBOL_PROP_SWAP_TUESDAY);                     }
   bool              IsIncreasedSwapTuesday(void)                       const { return (bool)this.GetPropDoubleFlagINC(SYMBOL_PROP_SWAP_TUESDAY);                    }
   bool              IsDecreasedSwapTuesday(void)                       const { return (bool)this.GetPropDoubleFlagDEC(SYMBOL_PROP_SWAP_TUESDAY);                    }
   //--- установка контролируемой величины (1) прироста, (2) уменьшения, (3) контрольного уровня коэффициента начисления свопов для среды
   //--- получение (4) величины изменения коэффициента начисления свопов для среды,
   //--- получение флага изменения коэффициента начисления свопов для среды больше, чем на величину (5) прироста, (6) уменьшения
   void              SetControlSwapWednesdayInc(const double value)           { this.SetControlledValueINC(SYMBOL_PROP_SWAP_WEDNESDAY,::fabs(value));                }
   void              SetControlSwapWednesdayDec(const double value)           { this.SetControlledValueDEC(SYMBOL_PROP_SWAP_WEDNESDAY,::fabs(value));                }
   void              SetControlSwapWednesdayLevel(const double value)         { this.SetControlledValueLEVEL(SYMBOL_PROP_SWAP_WEDNESDAY,::fabs(value));              }
   double            GetValueChangedSwapWednesday(void)                 const { return this.GetPropDoubleChangedValue(SYMBOL_PROP_SWAP_WEDNESDAY);                   }
   bool              IsIncreasedSwapWednesday(void)                     const { return (bool)this.GetPropDoubleFlagINC(SYMBOL_PROP_SWAP_WEDNESDAY);                  }
   bool              IsDecreasedSwapWednesday(void)                     const { return (bool)this.GetPropDoubleFlagDEC(SYMBOL_PROP_SWAP_WEDNESDAY);                  }
   //--- установка контролируемой величины (1) прироста, (2) уменьшения, (3) контрольного уровня коэффициента начисления свопов для четверга
   //--- получение (4) величины изменения коэффициента начисления свопов для четверга,
   //--- получение флага изменения коэффициента начисления свопов для четверга больше, чем на величину (5) прироста, (6) уменьшения
   void              SetControlSwapThursdayInc(const double value)            { this.SetControlledValueINC(SYMBOL_PROP_SWAP_THURSDAY,::fabs(value));                 }
   void              SetControlSwapThursdayDec(const double value)            { this.SetControlledValueDEC(SYMBOL_PROP_SWAP_THURSDAY,::fabs(value));                 }
   void              SetControlSwapThursdayLevel(const double value)          { this.SetControlledValueLEVEL(SYMBOL_PROP_SWAP_THURSDAY,::fabs(value));               }
   double            GetValueChangedSwapThursday(void)                  const { return this.GetPropDoubleChangedValue(SYMBOL_PROP_SWAP_THURSDAY);                    }
   bool              IsIncreasedSwapThursday(void)                      const { return (bool)this.GetPropDoubleFlagINC(SYMBOL_PROP_SWAP_THURSDAY);                   }
   bool              IsDecreasedSwapThursday(void)                      const { return (bool)this.GetPropDoubleFlagDEC(SYMBOL_PROP_SWAP_THURSDAY);                   }
   //--- установка контролируемой величины (1) прироста, (2) уменьшения, (3) контрольного уровня коэффициента начисления свопов для пятницы
   //--- получение (4) величины изменения коэффициента начисления свопов для пятницы,
   //--- получение флага изменения коэффициента начисления свопов для пятницы больше, чем на величину (5) прироста, (6) уменьшения
   void              SetControlSwapFridayInc(const double value)              { this.SetControlledValueINC(SYMBOL_PROP_SWAP_FRIDAY,::fabs(value));                   }
   void              SetControlSwapFridayDec(const double value)              { this.SetControlledValueDEC(SYMBOL_PROP_SWAP_FRIDAY,::fabs(value));                   }
   void              SetControlSwapFridayLevel(const double value)            { this.SetControlledValueLEVEL(SYMBOL_PROP_SWAP_FRIDAY,::fabs(value));                 }
   double            GetValueChangedSwapFriday(void)                    const { return this.GetPropDoubleChangedValue(SYMBOL_PROP_SWAP_FRIDAY);                      }
   bool              IsIncreasedSwapFriday(void)                        const { return (bool)this.GetPropDoubleFlagINC(SYMBOL_PROP_SWAP_FRIDAY);                     }
   bool              IsDecreasedSwapFriday(void)                        const { return (bool)this.GetPropDoubleFlagDEC(SYMBOL_PROP_SWAP_FRIDAY);                     }
   //--- установка контролируемой величины (1) прироста, (2) уменьшения, (3) контрольного уровня коэффициента начисления свопов для субботы
   //--- получение (4) величины изменения коэффициента начисления свопов для субботы,
   //--- получение флага изменения коэффициента начисления свопов для субботы больше, чем на величину (5) прироста, (6) уменьшения
   void              SetControlSwapSaturdayInc(const double value)            { this.SetControlledValueINC(SYMBOL_PROP_SWAP_SATURDAY,::fabs(value));                 }
   void              SetControlSwapSaturdayDec(const double value)            { this.SetControlledValueDEC(SYMBOL_PROP_SWAP_SATURDAY,::fabs(value));                 }
   void              SetControlSwapSaturdayLevel(const double value)          { this.SetControlledValueLEVEL(SYMBOL_PROP_SWAP_SATURDAY,::fabs(value));               }
   double            GetValueChangedSwapSaturday(void)                  const { return this.GetPropDoubleChangedValue(SYMBOL_PROP_SWAP_SATURDAY);                    }
   bool              IsIncreasedSwapSaturday(void)                      const { return (bool)this.GetPropDoubleFlagINC(SYMBOL_PROP_SWAP_SATURDAY);                   }
   bool              IsDecreasedSwapSaturday(void)                      const { return (bool)this.GetPropDoubleFlagDEC(SYMBOL_PROP_SWAP_SATURDAY);                   }

//--- Возвращает торговый объект
   CTradeObj        *GetTradeObj(void)                                           { return &this.m_trade; }

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

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

В защищённом параметрическом конструкторе класса впишем сохранение новых свойств объекта-символа:

   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_MAINTENANCE)]= this.m_margin_rate.SellStopLimit.Maintenance;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_SUNDAY)]                      = this.SymbolSwapRatio(SUNDAY);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_MONDAY)]                      = this.SymbolSwapRatio(MONDAY);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_TUESDAY)]                     = this.SymbolSwapRatio(TUESDAY);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_WEDNESDAY)]                   = this.SymbolSwapRatio(WEDNESDAY);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_THURSDAY)]                    = this.SymbolSwapRatio(THURSDAY);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_FRIDAY)]                      = this.SymbolSwapRatio(FRIDAY);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_SATURDAY)]                    = this.SymbolSwapRatio(SATURDAY);
//--- Сохранение строковых свойств
   this.m_string_prop[this.IndexProp(SYMBOL_PROP_NAME)]                             = this.m_name;

Здесь в массив свойств объекта вписываются значения, получаемые методом SymbolSwapRatio(), который рассмотрим ниже.

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

Защищённый метод, возвращающий коэффициент начисления свопов для указанного дня недели:

//+------------------------------------------------------------------+
//|Возвращает коэффициент начисления свопов для указанного дня недели|
//+------------------------------------------------------------------+
double CSymbol::SymbolSwapRatio(ENUM_DAY_OF_WEEK day) const
  {
#ifdef __MQL4__ 
   return 0;
#else 
   switch(day)
     {
      case MONDAY    :  return ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_MONDAY);
      case TUESDAY   :  return ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_TUESDAY);
      case WEDNESDAY :  return ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_WEDNESDAY);
      case THURSDAY  :  return ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_THURSDAY);
      case FRIDAY    :  return ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_FRIDAY);
      case SATURDAY  :  return ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_SATURDAY);
      //---SUNDAY
      default        :  return (int)::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_SUNDAY);
     }
#endif 
  }
//+------------------------------------------------------------------+

Здесь: в MQL4 нету такого свойства символа — возвращаем ноль. Для MQL5 в зависимости от переданного в метод дня недели возвращаем соответствующее свойство символа. Метод служит для записи свойства символа в перечисление его вещественных свойств в конструкторе класса.

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

//+------------------------------------------------------------------+
//|Возвращает коэффициент начисления свопов для указанного дня недели|
//+------------------------------------------------------------------+
double CSymbol::SwapRatioDay(const ENUM_DAY_OF_WEEK day) const
  {
   switch(day)
     {
      case MONDAY    :  return this.SwapRatioMonday();
      case TUESDAY   :  return this.SwapRatioTuesday();
      case WEDNESDAY :  return this.SwapRatioWednesday();
      case THURSDAY  :  return this.SwapRatioThursday();
      case FRIDAY    :  return this.SwapRatioFriday();
      case SATURDAY  :  return this.SwapRatioSaturday();
      //---SUNDAY
      default        :  return this.SwapRatioSunday();
     }
  }
//+------------------------------------------------------------------+

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

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

      property==SYMBOL_PROP_PRICE_SENSITIVITY   ?  CMessage::Text(MSG_SYM_PROP_PRICE_SENSITIVITY)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),dg)
         )  :
      property==SYMBOL_PROP_SWAP_SUNDAY      ?  CMessage::Text(MSG_SYM_PROP_SWAP_SUNDAY)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
         #ifdef __MQL5__
         (::TerminalInfoInteger(TERMINAL_BUILD)<3260 ?  ": ("+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3260)+")" : 
                                                        ": "+this.GetSwapRatioDescription(SUNDAY))
         #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif 
         )  :
      property==SYMBOL_PROP_SWAP_MONDAY      ?  CMessage::Text(MSG_SYM_PROP_SWAP_MONDAY)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
         #ifdef __MQL5__
         (::TerminalInfoInteger(TERMINAL_BUILD)<3260 ?  ": ("+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3260)+")" : 
                                                        ": "+this.GetSwapRatioDescription(MONDAY))
         #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif 
         )  :
      property==SYMBOL_PROP_SWAP_TUESDAY     ?  CMessage::Text(MSG_SYM_PROP_SWAP_TUESDAY)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
         #ifdef __MQL5__
         (::TerminalInfoInteger(TERMINAL_BUILD)<3260 ?  ": ("+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3260)+")" : 
                                                        ": "+this.GetSwapRatioDescription(TUESDAY))
         #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif 
         )  :
      property==SYMBOL_PROP_SWAP_WEDNESDAY   ?  CMessage::Text(MSG_SYM_PROP_SWAP_WEDNESDAY)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
         #ifdef __MQL5__
         (::TerminalInfoInteger(TERMINAL_BUILD)<3260 ?  ": ("+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3260)+")" : 
                                                        ": "+this.GetSwapRatioDescription(WEDNESDAY))
         #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif 
         )  :
      property==SYMBOL_PROP_SWAP_THURSDAY    ?  CMessage::Text(MSG_SYM_PROP_SWAP_THURSDAY)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
         #ifdef __MQL5__
         (::TerminalInfoInteger(TERMINAL_BUILD)<3260 ?  ": ("+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3260)+")" : 
                                                        ": "+this.GetSwapRatioDescription(THURSDAY))
         #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif 
         )  :
      property==SYMBOL_PROP_SWAP_FRIDAY      ?  CMessage::Text(MSG_SYM_PROP_SWAP_FRIDAY)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
         #ifdef __MQL5__
         (::TerminalInfoInteger(TERMINAL_BUILD)<3260 ?  ": ("+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3260)+")" : 
                                                        ": "+this.GetSwapRatioDescription(FRIDAY))
         #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif 
         )  :
      property==SYMBOL_PROP_SWAP_SATURDAY    ?  CMessage::Text(MSG_SYM_PROP_SWAP_SATURDAY)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
         #ifdef __MQL5__
         (::TerminalInfoInteger(TERMINAL_BUILD)<3260 ?  ": ("+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3260)+")" : 
                                                        ": "+this.GetSwapRatioDescription(SATURDAY))
         #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif 
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

Здесь проверяется билд терминала и, если он ниже 3260, то возвращается текст, что такое свойство не поддерживается в данной версии. Иначе, для MQL5 выводится описание свойства, а для MQL4 — текст, что свойство не поддерживается.

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

//+------------------------------------------------------------------+
//| Возвращает описание коэффициента начисления свопов               |
//| для указанного дня недели                                        |
//+------------------------------------------------------------------+
string CSymbol::GetSwapRatioDescription(const ENUM_DAY_OF_WEEK day) const
  {
   double ratio=this.SwapRatioDay(day);
   return
     (
      ratio==0 ? CMessage::Text(MSG_SYM_PROP_SWAP_0)  :
      ratio==1 ? CMessage::Text(MSG_SYM_PROP_SWAP_1)  :
      ratio==3 ? CMessage::Text(MSG_SYM_PROP_SWAP_3)  : 
      ::DoubleToString(ratio,3)
     );
  }
//+------------------------------------------------------------------+

Здесь: сначала получаем значение свойства для переданного в метод дня недели, затем возвращаем либо текстовое описание значения свойства (если значение равно 0, 1 или 3), либо выводим double-значение, преобразованное в текст с тремя знаками после запятой. (Реально сколько выводить знаков после запятой покажет только время и тесты).

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

В файле \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh допишем в структуру объекта его новые свойства:

private:
   int               m_shift_coord_x;                          // Смещение координаты X относительно базового объекта
   int               m_shift_coord_y;                          // Смещение координаты Y относительно базового объекта
   struct SData
     {
      //--- Целочисленные свойства объекта
      int            id;                                       // Идентификатор элемента
      int            type;                                     // Тип графического элемента
      int            number;                                   // Номер элемента в списке
      long           chart_id;                                 // Идентификатор графика
      int            subwindow;                                // Номер подокна графика
      int            coord_x;                                  // X-координата элемента на графике
      int            coord_y;                                  // Y-координата элемента на графике
      int            width;                                    // Ширина элемента
      int            height;                                   // Высота элемента
      int            edge_right;                               // Правая граница элемента
      int            edge_bottom;                              // Нижняя граница элемента
      int            act_shift_left;                           // Отступ активной зоны от левого края элемента
      int            act_shift_top;                            // Отступ активной зоны от верхнего края элемента
      int            act_shift_right;                          // Отступ активной зоны от правого края элемента
      int            act_shift_bottom;                         // Отступ активной зоны от нижнего края элемента
      bool           movable;                                  // Флаг перемещаемости элемента
      bool           active;                                   // Флаг активности элемента
      bool           interaction;                              // Флаг взаимодействия элемента с внешней средой
      int            coord_act_x;                              // X-координата активной зоны элемента
      int            coord_act_y;                              // Y-координата активной зоны элемента
      int            coord_act_right;                          // Правая граница активной зоны элемента
      int            coord_act_bottom;                         // Нижняя граница активной зоны элемента
      long           zorder;                                   // Приоритет графического объекта на получение события нажатия мышки на графике
      bool           enabled;                                  // Флаг доступности элемента
      int            belong;                                   // Принадлежность графического элемента
      color          fore_color;                               // Цвет текста по умолчанию для всех объектов элемента управления
      int            bold_type;                                // Тип толщины шрифта
      int            border_style;                             // Стиль рамки элемента управления
      bool           autosize;                                 // Флаг автоматического изменения размера элемента управления под содержимое
      int            dock_mode;                                // Режим привязки границ элемента управления к контейнеру
      int            margin_top;                               // Промежуток сверху между полями данного и другого элемента управления
      int            margin_bottom;                            // Промежуток снизу между полями данного и другого элемента управления
      int            margin_left;                              // Промежуток слева между полями данного и другого элемента управления
      int            margin_right;                             // Промежуток справа между полями данного и другого элемента управления
      int            padding_top;                              // Промежуток сверху внутри элемента управления
      int            padding_bottom;                           // Промежуток снизу внутри элемента управления
      int            padding_left;                             // Промежуток слева внутри элемента управления
      int            padding_right;                            // Промежуток справа внутри элемента управления
      uchar          opacity;                                  // Непрозрачность элемента
      color          color_bg;                                 // Цвет фона элемента
      //--- Вещественные свойства объекта

      //--- Строковые свойства объекта
      uchar          name_obj[64];                             // Имя объекта-графического элемента
      uchar          name_res[64];                             // Имя графического ресурса
     };
   SData             m_struct_obj;                             // Структура объекта


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

   this.m_struct_obj.coord_act_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM);      // Нижняя граница активной зоны элемента
   this.m_struct_obj.belong=(int)this.GetProperty(CANV_ELEMENT_PROP_BELONG);                    // Принадлежность графического элемента
   this.m_struct_obj.zorder=this.GetProperty(CANV_ELEMENT_PROP_ZORDER);                         // Приоритет графического объекта на получение события нажатия мышки на графике
   this.m_struct_obj.fore_color=(color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR);          // Цвет текста по умолчанию для всех объектов элемента управления
   this.m_struct_obj.bold_type=(int)this.GetProperty(CANV_ELEMENT_PROP_BOLD_TYPE);              // Тип толщины шрифта
   this.m_struct_obj.border_style=(int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_STYLE);        // Стиль рамки элемента управления
   this.m_struct_obj.autosize=this.GetProperty(CANV_ELEMENT_PROP_AUTOSIZE);                     // Флаг автоматического изменения размера элемента управления под содержимое
   this.m_struct_obj.dock_mode=(int)this.GetProperty(CANV_ELEMENT_PROP_DOCK_MODE);              // Режим привязки границ элемента управления к контейнеру
   this.m_struct_obj.margin_top=(int)this.GetProperty(CANV_ELEMENT_PROP_MARGIN_TOP);            // Промежуток сверху между полями данного и другого элемента управления
   this.m_struct_obj.margin_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_MARGIN_BOTTOM);      // Промежуток снизу между полями данного и другого элемента управления
   this.m_struct_obj.margin_left=(int)this.GetProperty(CANV_ELEMENT_PROP_MARGIN_LEFT);          // Промежуток слева между полями данного и другого элемента управления
   this.m_struct_obj.margin_right=(int)this.GetProperty(CANV_ELEMENT_PROP_MARGIN_RIGHT);        // Промежуток справа между полями данного и другого элемента управления
   this.m_struct_obj.padding_top=(int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_TOP);          // Промежуток сверху внутри элемента управления
   this.m_struct_obj.padding_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_BOTTOM);    // Промежуток снизу внутри элемента управления
   this.m_struct_obj.padding_left=(int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_LEFT);        // Промежуток слева внутри элемента управления
   this.m_struct_obj.padding_right=(int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_RIGHT);      // Промежуток справа внутри элемента управления
   this.m_struct_obj.color_bg=this.m_color_bg;                                                  // Цвет фона элемента
   this.m_struct_obj.opacity=this.m_opacity;                                                    // Непрозрачность элемента


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

   this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.m_struct_obj.coord_act_bottom);           // Нижняя граница активной зоны элемента
   this.SetProperty(CANV_ELEMENT_PROP_BELONG,this.m_struct_obj.belong);                         // Принадлежность графического элемента
   this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR,this.m_struct_obj.fore_color);                 // Цвет текста по умолчанию для всех объектов элемента управления
   this.SetProperty(CANV_ELEMENT_PROP_BOLD_TYPE,this.m_struct_obj.bold_type);                   // Тип толщины шрифта
   this.SetProperty(CANV_ELEMENT_PROP_BORDER_STYLE,this.m_struct_obj.border_style);             // Стиль рамки элемента управления
   this.SetProperty(CANV_ELEMENT_PROP_AUTOSIZE,this.m_struct_obj.autosize);                     // Флаг автоматического изменения размера элемента управления под содержимое
   this.SetProperty(CANV_ELEMENT_PROP_DOCK_MODE,this.m_struct_obj.dock_mode);                   // Режим привязки границ элемента управления к контейнеру
   this.SetProperty(CANV_ELEMENT_PROP_MARGIN_TOP,this.m_struct_obj.margin_top);                 // Промежуток сверху между полями данного и другого элемента управления
   this.SetProperty(CANV_ELEMENT_PROP_MARGIN_BOTTOM,this.m_struct_obj.margin_bottom);           // Промежуток снизу между полями данного и другого элемента управления
   this.SetProperty(CANV_ELEMENT_PROP_MARGIN_LEFT,this.m_struct_obj.margin_left);               // Промежуток слева между полями данного и другого элемента управления
   this.SetProperty(CANV_ELEMENT_PROP_MARGIN_RIGHT,this.m_struct_obj.margin_right);             // Промежуток справа между полями данного и другого элемента управления
   this.SetProperty(CANV_ELEMENT_PROP_PADDING_TOP,this.m_struct_obj.padding_top);               // Промежуток сверху внутри элемента управления
   this.SetProperty(CANV_ELEMENT_PROP_PADDING_BOTTOM,this.m_struct_obj.padding_bottom);         // Промежуток снизу внутри элемента управления
   this.SetProperty(CANV_ELEMENT_PROP_PADDING_LEFT,this.m_struct_obj.padding_left);             // Промежуток слева внутри элемента управления
   this.SetProperty(CANV_ELEMENT_PROP_PADDING_RIGHT,this.m_struct_obj.padding_right);           // Промежуток справа внутри элемента управления
   this.SetZorder(this.m_struct_obj.zorder,false);                                              // Приоритет графического объекта на получение события нажатия мышки на графике
   this.m_color_bg=this.m_struct_obj.color_bg;                                                  // Цвет фона элемента
   this.m_opacity=this.m_struct_obj.opacity;                                                    // Непрозрачность элемента

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


Внесём в базовый класс WinForms-объектов в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh небольшую доработку.

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

//+------------------------------------------------------------------+
//|                                                  WinFormBase.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\Form.mqh"
#include "..\..\..\Services\Select.mqh"
//+------------------------------------------------------------------+

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

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

Поэтому впишем проверку на установленный флаг и вызов перерисовки объектов только в случае, если флаг установлен:

//+------------------------------------------------------------------+
//| Перерисовывает объект                                            |
//+------------------------------------------------------------------+
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;
      if(redraw)
         element.Redraw(redraw);
     }
//--- При установленном флаге перерисовки, и если это главный объект, к которому прикреплены остальные -
//--- перерисуем чарт для немедленного отображения изменений
   if(redraw && this.GetMain()==NULL)
      ::ChartRedraw(this.ChartID());
  }
//+------------------------------------------------------------------+


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

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

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

protected:

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

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

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

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

protected:
//--- Возвращает максимальное значение выхода границ Dock-объектов за пределы контейнера по (1) ширине, (2) высоте
   int               GetExcessMaxX(void);
   int               GetExcessMaxY(void);
//--- Устанавливает координату (1) X, (2) Y, (3) ширину, (4) высоту, (5) все параметры подложки

Также удалим и реализацию этих методов, написанную за пределами тела класса.

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

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

public:
//--- Возвращает подложку
   CGCnvElement     *GetUnderlay(void)                               { return this.m_underlay;              }
//--- Возвращает список прикреплённых объектов с типом WinForms базовый и выше
   CArrayObj        *GetListWinFormsObj(void);


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

//--- Конструкторы
                     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_NONE);
                        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();
  };
//+------------------------------------------------------------------+
//| Конструктор с указанием идентификатора чарта и подокна           |
//+------------------------------------------------------------------+
CPanel::CPanel(const long chart_id,
               const int subwindow,
               const string name,
               const int x,
               const int y,
               const int w,
               const int h) : CWinFormBase(chart_id,subwindow,name,x,y,w,h)
  {
   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_NONE);
   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();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+

В последнем конструкторе кроме установки типа рамки ещё и удалим её создание:

   this.Initialize();
   this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle());
   if(this.CreateUnderlayObj())
      this.SetUnderlayAsBase();


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

//+------------------------------------------------------------------+
//| Располагает привязанные объекты в порядке их Dock-привязки       |
//+------------------------------------------------------------------+
bool CPanel::ArrangeObjects(const bool redraw)
  {
//--- Если нет подложки у панели - возвращаем false
   if(this.m_underlay==NULL)
      return false;
//--- Получаем список прикреплённых объектов с типом WinForms базовый и выше
   CArrayObj *list=this.GetListWinFormsObj();
   CWinFormBase *prev=NULL, *obj=NULL, *elm=NULL;
   //--- Если включен режим автоизменения размеров
   if(this.AutoSize())
     {
      //--- Изменим размеры панели под первоначальные, а затем - под расположенные внутри неё объекты
      this.Resize(this.GetWidthInit(),this.GetHeightInit(),false);
      this.AutoSizeProcess(false);
      //--- В цикле во всем привязанным объектам
      for(int i=0;i<list.Total();i++)
        {
         //--- Получаем текущий и предыдущий элементы из списка
         obj=list.At(i);
         prev=list.At(i-1);
         //--- Если предыдущего элемента нет - устанавливаем подложку как предыдущий элемент
         if(prev==NULL)
            this.SetUnderlayAsBase();
         //--- Если объект не получен, или его тип меньше базового WinForms-объекта, или у текущего элемента нет подложки - идём далее
         if(obj==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() : 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());
        }
      this.Resize(this.GetWidthInit(),this.GetHeightInit(),false);
      this.AutoSizeProcess(false);
     }
   //--- Если режим автоизменения размеров отключен 
   else
     {
      //--- В цикле во всем привязанным объектам
      for(int i=0;i<list.Total();i++)
        {
         //--- Получаем текущий и предыдущий элементы из списка
         obj=list.At(i);
         prev=list.At(i-1);
         //--- Если предыдущего элемента нет - устанавливаем подложку как предыдущий элемент
         if(prev==NULL)
            this.SetUnderlayAsBase();
         //--- Если объект не получен, или его тип меньше базового WinForms-объекта, или у текущего элемента нет подложки - идём далее
         if(obj==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;
  }
//+------------------------------------------------------------------+

Здесь мы могли сделать всё в одном цикле — т.е., не разделяя на два блока с двумя отдельными, идентичными друг другу, циклами, и дополнительно проверять флаг автоизменения размеров панели. Это укоротит код вдвое. Но пока оставим так на случай, если далее придётся ещё дорабатывать блок кода с обработкой флага автоизменения размеров панели. И только когда всё будет отлажено и ясно, что изменений и доработок больше не потребуется, мы оптимизируем код этого метода.

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

//+------------------------------------------------------------------+
//| Подстраивает размеры элемента под его внутреннее содержимое      |
//+------------------------------------------------------------------+
bool CPanel::AutoSizeProcess(const bool redraw)
  {
//--- Получаем список прикреплённых объектов с типом WinForms базовый и выше
   CArrayObj *list=this.GetListWinFormsObj();
//--- Получаем индексы объектов в списке с максимальной и минимальной координатами по X и Y
   int imaxx=CSelect::FindGraphCanvElementMax(list,CANV_ELEMENT_PROP_COORD_X);
   int iminx=CSelect::FindGraphCanvElementMin(list,CANV_ELEMENT_PROP_COORD_X);
   int imaxy=CSelect::FindGraphCanvElementMax(list,CANV_ELEMENT_PROP_COORD_Y);
   int iminy=CSelect::FindGraphCanvElementMin(list,CANV_ELEMENT_PROP_COORD_Y);
//--- Получаем из списка объекты с максимальной и минимальной координатами по X и Y
   CWinFormBase *maxx=list.At(imaxx);
   CWinFormBase *minx=list.At(iminx);
   CWinFormBase *maxy=list.At(imaxy);
   CWinFormBase *miny=list.At(iminy);
//--- Если хотя бы один из четырёх объектов не получен - возвращаем false
   if(maxx==NULL || minx==NULL || maxy==NULL || miny==NULL)
      return false;
//--- Получаем минимальную координату по X и Y
   int min_x=minx.CoordX();
   int min_y=fmin(miny.CoordY(),maxy.BottomEdge());
//--- Рассчитываем общую ширину и высоту всех прикреплённых объектов
   int w=maxx.RightEdge()-min_x;
   int h=int(fmax(miny.CoordY(),maxy.BottomEdge())-min_y);
//--- Рассчитываем количество пикселей, на которые необходимо изменить размер панели по ширине и высоте
   int excess_x=w-this.m_underlay.Width();
   int excess_y=h-this.m_underlay.Height();
//--- Рассчитываем смещение, на которое необходимо будет сместить прикреплённые объекты
   int shift_x=m_underlay.CoordX()-min_x;
   int shift_y=m_underlay.CoordY()-min_y;
//--- Если размеры панели менять не нужно - возвращаем true
   if(excess_x==0 && excess_y==0)
      return true;
//--- Если необходимо сместить прикреплённые объекты внутри панели по координате X или Y
   bool res=true;
   if(shift_x>0 || shift_y>0)
     {
      //--- В цикле по всем прикреплённым объектам
      for(int i=0;i<list.Total();i++)
        {
         //--- получаем очередной объект.
         CWinFormBase *obj=list.At(i);
         if(obj==NULL)
            continue;
         //--- Если объект нужно сместить по горизонтали - записываем в res результат смещения
         if(shift_x>0)
            res &=obj.Move(obj.CoordX()+shift_x,obj.CoordY());
         //--- Если объект нужно сместить по вертикали - записываем в res результат смещения
         if(shift_y>0)
            res &=obj.Move(obj.CoordX(),obj.CoordY()+shift_y);
         //--- Устанавливаем новые относительные координаты объекта по X и Y
         obj.SetCoordXRelative(obj.CoordX()-this.m_underlay.CoordX());
         obj.SetCoordYRelative(obj.CoordY()-this.m_underlay.CoordY());
        }
     }
//--- Возвращаем результат изменения размеров панели
   return
     (
      //--- Если не удалось сместить хотя бы один привязанный объект - возвращаем false
      !res ? false :
      //--- Иначе, если только увеличение размеров
      this.AutoSizeMode()==CANV_ELEMENT_AUTO_SIZE_MODE_GROW ? 
      this.Resize(this.Width()+(excess_x>0  ? excess_x : 0),this.Height()+(excess_y>0  ? excess_y : 0),redraw) :
      //--- если увеличение и уменьшение размеров
      this.Resize(this.Width()+(excess_x!=0 ? excess_x : 0),this.Height()+(excess_y!=0 ? excess_y : 0),redraw)
     );
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает список прикреплённых объектов                         |
//| с типом WinForms базовый и выше                                  |
//+------------------------------------------------------------------+
CArrayObj *CPanel::GetListWinFormsObj(void)
  {
   return CSelect::ByGraphCanvElementProperty(this.GetListElements(),CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE,EQUAL_OR_MORE);
  }
//+------------------------------------------------------------------+

Метод просто возвращает список, в котором содержатся только объекты с типом WinForms Base, или выше , т.е. — либо базовый WinForms-объект, либо его наследники. Объекты выбираются фильтрацией по типу из общего списка всех привязанных к панели объектов при помощи класса CSelect.

Проведём оптимизацию и устраним ошибки логики в методах создания WinForms-объектов "Панель" класса CGraphElementsCollection
в файле \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh.

У нас были допущены ошибки в том, что рамка объекта рисовалась два раза, а цвет рамки мог быть для неё не установлен. Кроме того, могли не устанавливаться и размеры рамки панели, если они передавались в методы как значение по умолчанию, равное -1. У нас не было проверки на это значение, поэтому оно и устанавливалось вместо того, чтобы при значении -1 устанавливать умолчательные значения размеров рамки.

Во все методы создания панелей были внесены такие, либо аналогичные по логике изменения:

//--- Создаёт объект-графический объект WinForms Panel на канвасе на указанном графике и подокне с заливкой вертикальным градиентом
   int               CreatePanelVGradient(const long chart_id,
                                          const int subwindow,
                                          const string name,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          color &clr[],
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity,
                                          const int  frame_width=-1,
                                          ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL,
                                          const bool shadow=false,
                                          const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CPanel *obj=new CPanel(chart_id,subwindow,name,x,y,w,h);
                        ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id);
                        if(res==ADD_OBJ_RET_CODE_ERROR)
                           return WRONG_VALUE;
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetColorsBackground(clr);
                        obj.SetColorFrame(obj.ColorBackground());
                        obj.SetBorderStyle(frame_style);
                        obj.SetOpacity(opacity,false);
                        obj.SetFrameWidthAll(frame_width==WRONG_VALUE ? DEF_FRAME_WIDTH_SIZE : frame_width);
                        //--- Установим флаг рисования тени
                        obj.SetShadow(shadow);
                        if(shadow)
                          {
                           color clrS=obj.ChangeColorLightness(obj.ChangeColorSaturation(obj.ColorBackground(),-100),-20);
                           obj.DrawShadow(3,3,clrS,CLR_DEF_SHADOW_OPACITY,DEF_SHADOW_BLUR);
                          }
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        if(redraw)
                          {
                           obj.Erase(clr,opacity,true,false,redraw);
                           obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                          } 
                        obj.SetActiveAreaShift(obj.FrameWidthLeft(),obj.FrameWidthBottom(),obj.FrameWidthRight(),obj.FrameWidthTop());
                        obj.Done();
                        return obj.ID();
                       }

Устанавливаем цвет рамки равный цвету фона панели (первому из градиента, либо единственному). При установке размеров рамки проверяем какое значение передано в метод и, если -1, то устанавливаем значение по умолчанию, записанное в макроподстановке DEF_FRAME_WIDTH_SIZE в файле Defines.mqh. Если установлен флаг перерисовки, то закрашиваем панель цветом фона, либо градиентом и рисуем сверху очерчивающий прямоугольник. При этом рисование рамки производится в виртуальном методе Erase() класса CPanel.

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


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

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

Как будем тестировать? Так как теперь привязанные к контейнеру объекты-панели создаются по умолчанию без рамки, то после их создания в обработчике OnInit(), укажем для каждой из них ширину рамки и её тип. Цвет каждой последующей панели будет светлее на рассчитанное от индекса цикла значение (цвет фона, осветлённый на величину, равную индекс цикла * 4). Это нужно для того, чтобы наглядно было видно изменение расположения панелей в контейнере при изменении способа их привязки:

//+------------------------------------------------------------------+
//| 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,false);
   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));
         if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,pnl,x,y,80,40,C'0xCD,0xDA,0xD7',200,true,false))
           {
            CPanel *obj=pnl.GetElement(i);
            if(obj==NULL)
               continue;
            obj.SetFrameWidthAll(3);
            obj.SetBorderStyle(FRAME_STYLE_BEVEL);
            obj.SetColorBackground(obj.ChangeColorLightness(obj.ColorBackground(),4*i));
           }
        }
      pnl.Redraw(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+


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


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


Что дальше

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

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

К содержанию

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

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


Прикрепленные файлы |
MQL5.zip (4425.23 KB)
Разработка торгового советника с нуля (Часть 9): Концептуальный скачок (II) Разработка торгового советника с нуля (Часть 9): Концептуальный скачок (II)
Размещение Chart Trade в плавающем окне. В предыдущей статье мы создали базовую систему для использования шаблонов внутри плавающего окна.
Индикаторы с интерактивным управлением на графике Индикаторы с интерактивным управлением на графике
Новый взгляд на интерфейс индикаторов. Главное — удобство. Перепробовав за долгие годы десятки различных торговых стратегий, а так же, протестировав сотни различных индикаторов, я сделал для себя некоторые выводы, которыми хочу с вами поделиться в этой статье.
Нейросети — это просто (Часть 16): Практическое использование кластеризации Нейросети — это просто (Часть 16): Практическое использование кластеризации
В предыдущей статье мы построили класс для кластеризации данных. В этой статье я хочу с Вами поделиться вариантами возможного использования полученных результатов для решения практических задач трейдинга.
Разработка торгового советника с нуля (Часть 8): Концептуальный скачок (I) Разработка торгового советника с нуля (Часть 8): Концептуальный скачок (I)
Как максимально просто реализовать новый функционал? В данной статье мы сделаем шаг назад, а затем два шага вперед.