DoEasy. Элементы управления (Часть 7): Элемент управления "Текстовая метка"

Artyom Trishkin | 3 июня, 2022

Содержание


Концепция

В любой программе не обойтись без вывода информации на экран. В MS Visual Studio для вывода текстов используются, наряду с другими, элемент управления Label — текстовая метка. В терминале MetaTrader 5 тоже есть графический объект "Текстовая метка". Кроме того, все создаваемые нами графические элементы, служащие для создания графических интерфейсов или графического оформления программ в терминале, тоже дают возможность вывода текста на канвас. Но это не всегда удобно. Поэтому сегодня мы сделаем самостоятельный элемент управления "Текстовая метка".

Такой объект будет иметь возможность позиционирования в любом месте своего контейнера, а его собственный функционал будет повторять некоторый функционал текстовой метки MS Visual Studio — мы сможем задать для выводимого текста параметры шрифта, позиционирование надписи в границах объекта "текстовая метка". В свою очередь размер объекта может как задаваться с указанными шириной и высотой, так и автоматически подстраивать свой размер под размер используемого для надписи шрифта. Кроме того, мы сможем использовать рамку объекта (прямоугольник, обрамляющий весь объект "текстовая метка" по его границам). Рамка объекта может быть как плоской, так и объёмной. Всё это даст нам достаточно широкие возможности для вывода текста в соответствующем оформлении внутри элементов GUI программы.

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


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

В первую очередь добавим новые текстовые сообщения в библиотеку.

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

   MSG_GRAPH_ELEMENT_TYPE_WF_PANEL,                   // Элемент управления Panel
   MSG_GRAPH_ELEMENT_TYPE_WF_LABEL,                   // Элемент управления Label
   MSG_GRAPH_OBJ_BELONG_PROGRAM,                      // Графический объект принадлежит программе

...

//--- CPanel
   MSG_PANEL_OBJECT_ERR_FAILED_CREATE_UNDERLAY_OBJ,   // Не удалось создать объект-подложку
   MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE,           // Ошибка. Создаваемый объект должен иметь тип WinForms Base или быть его наследником

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

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

   {"Элемент управления \"Panel\"","Control element \"Panel\""},
   {"Элемент управления \"Label\"","Control element \"Label\""},
   {"Графический объект принадлежит программе","The graphic object belongs to the program"},

...

//--- CPanel
   {"Не удалось создать объект-подложку","Failed to create underlay object"},
   {"Ошибка. Создаваемый объект должен иметь тип WinForms Base или быть его наследником","Error. The object being created must be of type WinForms Base or be derived from it"},
   
  };
//+---------------------------------------------------------------------+


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

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

//--- Параметры канваса
#define PAUSE_FOR_CANV_UPDATE          (16)                       // Частота обновления канваса
#define CLR_CANV_NULL                  (0x00FFFFFF)               // Ноль для канваса с альфа-каналом
#define CLR_DEF_FORE_COLOR             (C'0x2D,0x43,0x48')        // Цвет по умолчанию для текстов объектов на канвасе
#define CLR_DEF_FORE_COLOR_OPACITY     (255)                      // Непрозрачность цвета по умолчанию для текстов объектов на канвасе
#define CLR_DEF_OPACITY                (200)                      // Непрозрачность цвета по умолчанию для объектов на канвасе
#define CLR_DEF_SHADOW_COLOR           (C'0x6B,0x6B,0x6B')        // Цвет по умолчанию для теней объектов на канвасе
#define CLR_DEF_SHADOW_OPACITY         (127)                      // Непрозрачность цвета по умолчанию для теней объектов на канвасе
#define DEF_SHADOW_BLUR                (4)                        // Размытие по умолчанию для теней объектов на канвасе
#define DEF_FONT                       ("Calibri")                // Шрифт по умолчанию
#define DEF_FONT_SIZE                  (8)                        // Размер шрифта по умолчанию
#define OUTER_AREA_SIZE                (16)                       // Размер одной стороны внешней области вокруг рабочего пространства формы
#define DEF_FRAME_WIDTH_SIZE           (3)                        // Ширина рамки формы/панели/окна по умолчанию
//--- Параметры графических объектов


В список типов объектов библиотеки впишем новый тип — текстовая метка:

//+------------------------------------------------------------------+
//| Список типов объектов библиотеки                                 |
//+------------------------------------------------------------------+
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_GWF_LABEL,                                      // Тип объекта "WinForms Label"
//--- Анимация


Список типов графических элементов тоже дополним новым элементом — текстовой меткой:

//+------------------------------------------------------------------+
//| Список типов графических элементов                               |
//+------------------------------------------------------------------+
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
   GRAPH_ELEMENT_TYPE_WF_LABEL,                       // Windows Forms Label
  };
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Целочисленные свойства графического элемента на канвасе          |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROP_INTEGER
  {
   //--- ...
   
   CANV_ELEMENT_PROP_FORE_COLOR,                      // Цвет текста по умолчанию для всех объектов элемента управления
   CANV_ELEMENT_PROP_FORE_COLOR_OPACITY,              // Непрозрачность цвета текста по умолчанию для всех объектов элемента управления
   CANV_ELEMENT_PROP_BOLD_TYPE,                       // Тип толщины шрифта
   CANV_ELEMENT_PROP_BORDER_STYLE,                    // Стиль рамки элемента управления
   CANV_ELEMENT_PROP_AUTOSIZE,                        // Флаг автоматического изменения размера элемента управления под содержимое
   CANV_ELEMENT_PROP_AUTOSIZE_MODE,                   // Режим автоматического изменения размера элемента управления под содержимое
   CANV_ELEMENT_PROP_AUTOSCROLL,                      // Флаг автоматического появления полосы прокрутки
   CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_W,             // Ширина поля вокруг элемента при автоматической прокрутке
   CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_H,             // Высота поля вокруг элемента при автоматической прокрутке
   CANV_ELEMENT_PROP_DOCK_MODE,                       // Режим привязки границ элемента управления к контейнеру
   CANV_ELEMENT_PROP_MARGIN_TOP,                      // Промежуток сверху между полями данного и другого элемента управления
   
   //--- ...

   CANV_ELEMENT_PROP_PADDING_RIGHT,                   // Промежуток справа внутри элемента управления
   CANV_ELEMENT_PROP_TEXT_ALIGN,                      // Положение текста в границах текстовой метки
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (44)          // Общее количество целочисленных свойств
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Количество неиспользуемых в сортировке целочисленных свойств
//+------------------------------------------------------------------+

Общее количество целочисленных свойств, соответственно, увеличим с 38 до 44.


В список строковых свойств графического элемента на канвасе впишем новое свойство — "Текст графического элемента" и увеличим общее количество строковых свойств с 2 до 3:

//+------------------------------------------------------------------+
//| Строковые свойства графического элемента на канвасе              |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROP_STRING
  {
   CANV_ELEMENT_PROP_NAME_OBJ = (CANV_ELEMENT_PROP_INTEGER_TOTAL+CANV_ELEMENT_PROP_DOUBLE_TOTAL), // Имя объекта-графического элемента
   CANV_ELEMENT_PROP_NAME_RES,                        // Имя графического ресурса
   CANV_ELEMENT_PROP_TEXT,                            // Текст графического элемента
  };
#define CANV_ELEMENT_PROP_STRING_TOTAL  (3)           // Общее количество строковых свойств
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Возможные критерии сортировки графических элементов на канвасе   |
//+------------------------------------------------------------------+
#define FIRST_CANV_ELEMENT_DBL_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP)
#define FIRST_CANV_ELEMENT_STR_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP)
enum ENUM_SORT_CANV_ELEMENT_MODE
  {
//--- Сортировка по целочисленным свойствам
   SORT_BY_CANV_ELEMENT_ID = 0,                       // Сортировать по идентификатору элемента
   
   //--- ...

   SORT_BY_CANV_ELEMENT_FORE_COLOR,                   // Сортировать по цвету текста по умолчанию для всех объектов элемента управления
   SORT_BY_CANV_ELEMENT_FORE_COLOR_OPACITY,           // Сортировать по непрозрачности цвета текста по умолчанию для всех объектов элемента управления
   SORT_BY_CANV_ELEMENT_BOLD_TYPE,                    // Сортировать по типу толщины шрифта
   SORT_BY_CANV_ELEMENT_BORDER_STYLE,                 // Сортировать по стилю рамки элемента управления
   SORT_BY_CANV_ELEMENT_AUTOSIZE,                     // Сортировать по флагу автоматического изменения размера элемента управления под содержимое
   SORT_BY_CANV_ELEMENT_AUTOSIZE_MODE,                // Сортировать по режиму автоматического изменения размера элемента управления под содержимое
   SORT_BY_CANV_ELEMENT_AUTOSCROLL,                   // Сортировать по флагу автоматического появления полосы прокрутки
   SORT_BY_CANV_ELEMENT_AUTOSCROLL_MARGIN_W,          // Сортировать по ширине поля вокруг элемента при автоматической прокрутке
   SORT_BY_CANV_ELEMENT_AUTOSCROLL_MARGIN_H,          // Сортировать по высоте поля вокруг элемента при автоматической прокрутке
   SORT_BY_CANV_ELEMENT_DOCK_MODE,                    // Сортировать по режиму привязки границ элемента управления к контейнеру
   
   //--- ...

   SORT_BY_CANV_ELEMENT_PADDING_RIGHT,                // Сортировать по промежутку справа внутри элемента управления
   SORT_BY_CANV_ELEMENT_TEXT_ALIGN,                   // Сортировать по положению текста в границах текстовой метки
//--- Сортировка по вещественным свойствам

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

Теперь мы сможем сортировать, выбирать и фильтровать все графические элементы по новым свойствам.


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

В файле объекта-формы \MQL5\Include\DoEasy\Objects\Graph\Form.mqh перенесём переменные из приватной секции

//+------------------------------------------------------------------+
//| Класс объекта "форма"                                            |
//+------------------------------------------------------------------+
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) геометрических кадров анимаций

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

protected:
   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 относительно курсора
   CArrayObj         m_list_tmp;                               // Список для размещения указателей
   int               m_frame_width_left;                       // Ширина рамки формы слева
   int               m_frame_width_right;                      // Ширина рамки формы справа
   int               m_frame_width_top;                        // Ширина рамки формы сверху
   int               m_frame_width_bottom;                     // Ширина рамки формы снизу
   int               m_init_x;                                 // Координата X формы при её создании
   int               m_init_y;                                 // Координата Y формы при её создании
   int               m_init_w;                                 // Ширина формы при её создании
   int               m_init_h;                                 // Высота формы при её создании
//--- Инициализирует переменные
   virtual 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);
//--- Создаёт новый присоединённый элемент и добавляет его в список присоединённых объектов
   virtual 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:


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

В файле \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.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:


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

public:
//--- ...
   
//--- ...

//--- Конструкторы
                     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.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR,clr);                               }
   color             ForeColor(void)                           const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR);                     }
//--- (1) Устанавливает, (2) возвращает непрозрачность цвета текста по умолчанию всех объектов на панели
   void              SetForeColorOpacity(const uchar value)          { this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY,value);                     }
   uchar             ForeColorOpacity(void)                    const { return (uchar)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY);             }
//--- (1) Устанавливает, (2) возвращает текст элемента
   virtual void      SetText(const string text)                      { this.SetProperty(CANV_ELEMENT_PROP_TEXT,text);                                    }
   string            Text(void)                                const { return this.GetProperty(CANV_ELEMENT_PROP_TEXT);                                  }
//--- (1) Устанавливает, (2) возвращает угол расположения (тип выравнивания) текста на элементе
   void              SetTextAlign(const ENUM_ANCHOR_POINT anchor)    { this.SetProperty(CANV_ELEMENT_PROP_TEXT_ALIGN,anchor);                            }
   ENUM_ANCHOR_POINT TextAlign(void)                           const { return (ENUM_ANCHOR_POINT)this.GetProperty(CANV_ELEMENT_PROP_TEXT_ALIGN);         }
//--- (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 (ENUM_FW_TYPE)this.GetProperty(CANV_ELEMENT_PROP_BOLD_TYPE);               }
//--- (1) Устанавливает, (2) возвращает стиль рамки
   void              SetBorderStyle(const ENUM_FRAME_STYLE style)    { this.SetProperty(CANV_ELEMENT_PROP_BORDER_STYLE,style);                           }
   ENUM_FRAME_STYLE  BorderStyle(void)                         const { return (ENUM_FRAME_STYLE)this.GetProperty(CANV_ELEMENT_PROP_BORDER_STYLE);        }

//--- (1) Устанавливает, (2) возвращает флаг автоматического изменения размера элемента под содержимое
   virtual void      SetAutoSize(const bool flag,const bool redraw)  { this.SetProperty(CANV_ELEMENT_PROP_AUTOSIZE,flag);                                }
   bool              AutoSize(void)                                  { return (bool)this.GetProperty(CANV_ELEMENT_PROP_AUTOSIZE);                        }
//--- (1) Устанавливает, (2) возвращает флаг автоматического появления полосы прокрутки
   virtual void      SetAutoScroll(const bool flag,const bool redraw){ this.SetProperty(CANV_ELEMENT_PROP_AUTOSCROLL,flag);                              }
   bool              AutoScroll(void)                                { return (bool)this.GetProperty(CANV_ELEMENT_PROP_AUTOSCROLL);                      }
   
//--- (1) Устанавливает, (2) возвращает режим привязки границ элемента к контейнеру
   virtual void      SetDockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode,const bool redraw)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_DOCK_MODE,mode);
                       }
   ENUM_CANV_ELEMENT_DOCK_MODE DockMode(void)                  const { return (ENUM_CANV_ELEMENT_DOCK_MODE)this.GetProperty(CANV_ELEMENT_PROP_DOCK_MODE);}
   
//--- Устанавливает промежуток (1) слева, (2) сверху, (3) справа, (4) снизу, (5) со всех сторон между полями данного и другого элемента управления
   void              SetMarginLeft(const int value)                  { this.SetProperty(CANV_ELEMENT_PROP_MARGIN_LEFT,value);                            }
   void              SetMarginTop(const int value)                   { this.SetProperty(CANV_ELEMENT_PROP_MARGIN_TOP,value);                             }
   void              SetMarginRight(const int value)                 { this.SetProperty(CANV_ELEMENT_PROP_MARGIN_RIGHT,value);                           }
   void              SetMarginBottom(const int value)                { this.SetProperty(CANV_ELEMENT_PROP_MARGIN_BOTTOM,value);                          }
   void              SetMarginAll(const int value)
                       {
                        this.SetMarginLeft(value); this.SetMarginTop(value); this.SetMarginRight(value); this.SetMarginBottom(value);
                       }
   void              SetMargin(const int left,const int top,const int right,const int bottom)
                       {
                        this.SetMarginLeft(left); this.SetMarginTop(top); this.SetMarginRight(right); this.SetMarginBottom(bottom);
                       }
//--- Возвращает промежуток (1) слева, (2) сверху, (3) справа, (4) снизу между полями данного и другого элемента управления
   int               MarginLeft(void)                          const { return (int)this.GetProperty(CANV_ELEMENT_PROP_MARGIN_LEFT);                      }
   int               MarginTop(void)                           const { return (int)this.GetProperty(CANV_ELEMENT_PROP_MARGIN_TOP);                       }
   int               MarginRight(void)                         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_MARGIN_RIGHT);                     }
   int               MarginBottom(void)                        const { return (int)this.GetProperty(CANV_ELEMENT_PROP_MARGIN_BOTTOM);                    }

//--- Устанавливает промежуток (1) слева, (2) сверху, (3) справа, (4) снизу, (5) со всех сторон внутри элемента управления
   virtual void      SetPaddingLeft(const uint value)
                       {
                        int padding=((int)value<this.m_frame_width_left ? this.m_frame_width_left : (int)value);
                        this.SetProperty(CANV_ELEMENT_PROP_PADDING_LEFT,padding);
                       }
   virtual void      SetPaddingTop(const uint value)
                       {
                        int padding=((int)value<this.m_frame_width_top ? this.m_frame_width_top : (int)value);
                        this.SetProperty(CANV_ELEMENT_PROP_PADDING_TOP,padding);
                       }
   virtual void      SetPaddingRight(const uint value)
                       {
                        int padding=((int)value<this.m_frame_width_right ? this.m_frame_width_right : (int)value);
                        this.SetProperty(CANV_ELEMENT_PROP_PADDING_RIGHT,padding);
                       }
   virtual void      SetPaddingBottom(const uint value)
                       {
                        int padding=((int)value<this.m_frame_width_bottom ? this.m_frame_width_bottom : (int)value);
                        this.SetProperty(CANV_ELEMENT_PROP_PADDING_BOTTOM,padding);
                       }
   virtual void      SetPaddingAll(const uint value)
                       {
                        this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value);
                       }
   virtual void      SetPadding(const int left,const int top,const int right,const int bottom)
                       {
                        this.SetPaddingLeft(left); this.SetPaddingTop(top); this.SetPaddingRight(right); this.SetPaddingBottom(bottom);
                       }
   
//--- Устанавливает ширину рамки элемента (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);
                       }
   virtual void      SetFrameWidth(const uint left,const uint top,const uint right,const uint bottom)
                       {
                        this.SetFrameWidthLeft(left); this.SetFrameWidthTop(top); this.SetFrameWidthRight(right); this.SetFrameWidthBottom(bottom);
                       }
   
//--- Возвращает ширину рамки элемента (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 (int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_LEFT);                     }
   int               PaddingTop(void)                          const { return (int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_TOP);                      }
   int               PaddingRight(void)                        const { return (int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_RIGHT);                    }
   int               PaddingBottom(void)                       const { return (int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_BOTTOM);                   }
   
  };
//+------------------------------------------------------------------+

Во всех переделанных методах мы теперь записываем значения и получаем их не в переменные, а в перечисления свойств объектов при помощи методов SetProperty() и GetProperty(), как и было изначально описано в концепции построения объектов библиотеки в первой статье её описания.

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

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
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.SetText("");
   this.SetForeColor(CLR_DEF_FORE_COLOR);
   this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY);
   this.SetFontBoldType(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;
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Устанавливает флаг шрифта Bold                                   |
//+------------------------------------------------------------------+
void CWinFormBase::SetBold(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
     {
      this.SetFontBoldType(FW_TYPE_BOLD);
      CGCnvElement::SetFontFlags(flags | FW_BOLD);
     }
   else
      this.SetFontBoldType(FW_TYPE_NORMAL);
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Устанавливает тип толщины шрифта                                 |
//+------------------------------------------------------------------+
void CWinFormBase::SetFontBoldType(ENUM_FW_TYPE type)
  {
   this.SetProperty(CANV_ELEMENT_PROP_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;
     }
  }
//+------------------------------------------------------------------+


Класс элемента управления "Текстовая метка"

В каталоге библиотеки \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ создадим новый файл Label.mqh класса CLabel. Базовым классом должен являться класс CWinFormBase, файл которого должен быть подключен к файлу нового созданного класса:

//+------------------------------------------------------------------+
//|                                                        Label.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 "..\..\WForms\WinFormBase.mqh"
//+------------------------------------------------------------------+
//| Класс объекта Label элементов управления WForms                  |
//+------------------------------------------------------------------+
class CLabel : public CWinFormBase
  {
  }

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

//+------------------------------------------------------------------+
//| Класс объекта Label элементов управления WForms                  |
//+------------------------------------------------------------------+
class CLabel : public CWinFormBase
  {
private:
//--- Автоматически устанавливает ширину и высоту элемента
   void              AutoSetWH(void);
protected:
//--- Инициализирует переменные
   virtual void      Initialize(void);
   
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);
//--- Устанавливает текст элемента
   virtual void      SetText(const string text)
                       {
                        CWinFormBase::SetText(text);
                        if(this.AutoSize())
                           this.AutoSetWH();
                       }

//--- Конструкторы
                     CLabel(const long chart_id,
                            const int subwindow,
                            const string name,
                            const int x,
                            const int y,
                            const int w,
                            const int h);
  };
//+------------------------------------------------------------------+

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

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

//--- Устанавливает текст элемента
   virtual void      SetText(const string text)
                       {
                        CWinFormBase::SetText(text);
                        if(this.AutoSize())
                           this.AutoSetWH();
                       }


Класс имеет параметрический конструктор и, соответственно, автоматически создаются конструктор и деструктор по умолчанию.

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

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CLabel::CLabel(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_LABEL);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_LABEL);
   this.m_type=OBJECT_DE_TYPE_GWF_LABEL;
   this.SetCoordX(x);
   this.SetCoordY(y);
   this.SetWidth(w);
   this.SetHeight(h);
   this.Initialize();
   if(this.AutoSize())
      this.AutoSetWH();
   this.SetWidthInit(this.Width());
   this.SetHeightInit(this.Height());
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.Redraw(false);
  }
//+------------------------------------------------------------------+

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

Виртуальный метод, инициализирующий переменные:

//+------------------------------------------------------------------+
//| Инициализирует переменные                                        |
//+------------------------------------------------------------------+
void CLabel::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;
//--- Ширина рамки объекта с каждой его стороны по умолчанию равна 1 пиксель
   this.m_frame_width_right=1;
   this.m_frame_width_left=1;
   this.m_frame_width_top=1;
   this.m_frame_width_bottom=1;
//--- У объекта нет градиентной заливки (как вертикальной, так и горизонтальной)
   this.m_gradient_v=false;
   this.m_gradient_c=false;
//--- Сбросим все "рабочие" флаги и переменные
   this.m_mouse_state_flags=0;
   this.m_offset_x=0;
   this.m_offset_y=0;
   CGCnvElement::SetInteraction(false);
//--- Создадим объект анимации и добавим его в список для хранения таких объектов
   this.m_animations=new CAnimations(CGCnvElement::GetObject());
   this.m_list_tmp.Add(this.m_animations);
//--- Устанавливаем прозрачный цвет для фона объекта
   this.SetColorBackground(CLR_CANV_NULL);
   this.SetOpacity(0);
//--- Установим цвет и непрозрачность текста по умолчанию и отсутствие рамки объекта
   this.SetForeColor(CLR_DEF_FORE_COLOR);
   this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY);
   this.SetBorderStyle(FRAME_STYLE_NONE);
//--- Установим параметры текста по умолчанию
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.SetText("");
   this.SetTextAnchor(FRAME_ANCHOR_LEFT_TOP);
   this.SetTextAlign(ANCHOR_LEFT_UPPER);
//--- Установим параметры объекта по умолчанию
   this.SetAutoSize(false,false);
   this.SetMargin(3,0,3,0);
   this.SetPaddingAll(0);
   this.SetEnabled(true);
   this.SetVisible(true,false);
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Очищает элемент с заполнением его цветом и непрозрачностью       |
//+------------------------------------------------------------------+
void CLabel::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(),255,this.BorderStyle());
//--- Обновляем элемент с указанным флагом необходимости перерисовки
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Очищает элемент заливкой градиентом                              |
//+------------------------------------------------------------------+
void CLabel::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(),255,this.BorderStyle());
//--- Обновляем элемент с указанным флагом необходимости перерисовки
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Полностью очищает элемент                                        |
//+------------------------------------------------------------------+
void CLabel::Erase(const bool redraw=false)
  {
//--- Полностью очищаем элемент с флагом необходимости перерисовки
   CGCnvElement::Erase(redraw);
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Перерисовывает объект                                            |
//+------------------------------------------------------------------+
void CLabel::Redraw(bool redraw)
  {
//--- Заполняем объект цветом фона с полной прозрачностью
   this.Erase(this.ColorBackground(),0,true);
   int x=0;
   int y=0;
//--- В зависимости от типа выравнивания текста на элементе
   switch(this.TextAlign())
     {
      //--- Текст рисуется в левом верхнем углу объекта
      case ANCHOR_LEFT_UPPER : 
        //--- Устанавливаем координату точки привязки текста
        x=this.FrameWidthLeft();
        y=this.FrameWidthTop();
        //--- Устанавливаем точку привязки текста слева вверху
        this.SetTextAnchor(FRAME_ANCHOR_LEFT_TOP);
        break;
      //--- Текст рисуется от левой грани объекта в центре по вертикали
      case ANCHOR_LEFT : 
        //--- Устанавливаем координату точки привязки текста
        x=this.FrameWidthLeft();
        y=this.Height()/2;
        //--- Устанавливаем точку привязки текста слева в центре
        this.SetTextAnchor(FRAME_ANCHOR_LEFT_CENTER);
        break;
      //--- Текст рисуется в левом нижнем углу объекта
      case ANCHOR_LEFT_LOWER : 
        //--- Устанавливаем координату точки привязки текста
        x=this.FrameWidthLeft();
        y=this.Height()-this.FrameWidthBottom();
        //--- Устанавливаем точку привязки текста слева внизу
        this.SetTextAnchor(FRAME_ANCHOR_LEFT_BOTTOM);
        break;
      //--- Текст рисуется по центру нижней грани объекта
      case ANCHOR_LOWER : 
        //--- Устанавливаем координату точки привязки текста
        x=this.Width()/2;
        y=this.Height()-this.FrameWidthBottom();
        //--- Устанавливаем точку привязки текста снизу в центре
        this.SetTextAnchor(FRAME_ANCHOR_CENTER_BOTTOM);
        break;
      //--- Текст рисуется в правом нижнем углу объекта
      case ANCHOR_RIGHT_LOWER : 
        //--- Устанавливаем координату точки привязки текста
        x=this.Width()-this.FrameWidthRight();
        y=this.Height()-this.FrameWidthBottom();
        //--- Устанавливаем точку привязки текста справа внизу
        this.SetTextAnchor(FRAME_ANCHOR_RIGHT_BOTTOM);
        break;
      //--- Текст рисуется от правой грани объекта в центре по вертикали
      case ANCHOR_RIGHT : 
        //--- Устанавливаем координату точки привязки текста
        x=this.Width()-this.FrameWidthRight();
        y=this.Height()/2;
        //--- Устанавливаем точку привязки текста справа в центре
        this.SetTextAnchor(FRAME_ANCHOR_RIGHT_CENTER);
        break;
      //--- Текст рисуется в правом верхнем углу объекта
      case ANCHOR_RIGHT_UPPER : 
        //--- Устанавливаем координату точки привязки текста
        x=this.Width()-this.FrameWidthRight();
        y=this.FrameWidthTop();
        //--- Устанавливаем точку привязки текста справа вверху
        this.SetTextAnchor(FRAME_ANCHOR_RIGHT_TOP);
        break;
      //--- Текст рисуется по центру верхней грани объекта
      case ANCHOR_UPPER : 
        //--- Устанавливаем координату точки привязки текста
        x=this.Width()/2;
        y=this.FrameWidthTop();
        //--- Устанавливаем точку привязки текста сверху в центре
        this.SetTextAnchor(FRAME_ANCHOR_CENTER_TOP);
        break;
      //--- Текст рисуется по центру объекта
      //---ANCHOR_CENTER
      default:
        //--- Устанавливаем координату точки привязки текста
        x=this.Width()/2;
        y=this.Height()/2;
        //--- Устанавливаем точку привязки текста в центре
        this.SetTextAnchor(FRAME_ANCHOR_CENTER);
        break;
     }
//--- Рисуем текст в установленных координатах объекта и точкой привязки текста и обновляем объект 
   this.Text(x,y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor());
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

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

Точки привязки текста наглядно расписаны и пояснены в справке по функции TextOut():



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

//+------------------------------------------------------------------+
//| Автоматически устанавливает ширину и высоту элемента             |
//+------------------------------------------------------------------+
void CLabel::AutoSetWH(void)
  {
//--- Определяем переменные для получения ширины и высоты надписи
   int w=0, h=0;
//--- Получаем ширину и высоту в зависимости от текста объекта
   CGCnvElement::TextSize(this.Text()!="" && this.Text()!=NULL ? this.Text() : " ",w,h);
//--- Прибавляем к полученной ширине значения Margin объекта слева и справа
   w+=(this.MarginLeft()+this.MarginRight());
//--- Если ширину получить не удалось - устанавливаем её в три пикселя
   if(w==0)
      w=3;
//--- Прибавляем к полученной высоте значения Margin объекта сверху и снизу
   h+=(this.MarginTop()+this.MarginBottom());
//--- Если высоту получить не удалось - устанавливаем её как величину "размер шрифта" * коэффициент
   if(h==0)
      h=(int)ceil(FontSize()*1.625);
//--- Устанавливаем ширину и высоту объекта из полученных значений
   this.SetWidth(w);
   this.SetHeight(h);
  }
//+------------------------------------------------------------------+

Логика метода расписана в комментариях к коду. Сначала получаем размер текста в зависимости от установленного для объекта текста и параметров шрифта. Если надпись "пустая", то для измерения будем использовать пробел (" "). Далее к полученным значениям ширины прибавляем значения Margin объекта слева и справа, а к значению высоты — Margin сверху и снизу. Если высоту текста получить не удалось, то придётся рассчитать её приблизительный размер умножением размера фонта, установленного для объекта, на коэффициент, подобранный мною эмпирическим образом. Т.е. я сопоставил значения величины объекта от размера шрифта в MS Visual Studio и взял среднее значение от нескольких измерений разных размеров, получив коэффициент 1.625. Честно говоря, другого, более точного способа я не знаю. Вполне может быть, что в последующем найду правильный способ расчёта размера объекта в зависимости от размера шрифта. Ну и по окончании всех расчётов объекту устанавливаются полученные значения ширины и высоты.

На этом создание объекта "Текстовая метка" завершено.

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

Откроем файл объекта-панели \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh и подключим к нему файл только что созданного объекта текстовой метки:

//+------------------------------------------------------------------+
//|                                                        Panel.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 "..\..\WForms\WinFormBase.mqh"
#include "..\..\WForms\Common Controls\Label.mqh"
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Класс объекта Panel элементов управления WForms                  |
//+------------------------------------------------------------------+
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;                 // Режим автоматического изменения размера элемента под содержимое
//--- Создаёт новый графический объект


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

public:
//--- Возвращает подложку
   CGCnvElement     *GetUnderlay(void)                               { return this.m_underlay;              }
//--- Возвращает список прикреплённых объектов с (1) любым, (2) указанным типом WinForms базовый и выше
   CArrayObj        *GetListWinFormsObj(void);
   CArrayObj        *GetListWinFormsObjByType(const ENUM_GRAPH_ELEMENT_TYPE type);
//--- Возвращает указатель на прикреплённый WinForms-объект с указанным типом по индексу
   CWinFormBase     *GetWinFormsObj(const ENUM_GRAPH_ELEMENT_TYPE type,const int index);
   
//--- Обновляет координаты (сдвигает канвас)


Методы SetAutoScroll() и AutoScroll() удалим из класса, так как они являются членами родительского класса CWinFormBase, уже переделаны под работу со свойствами объекта, а не переменными класса, и здесь излишни:

//--- Располагает привязанные объекты в порядке их 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) всех полей вокруг элемента управления при автоматической прокрутке


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

//--- Устанавливает (1) ширину, (2) высоту поля, (3) всех полей вокруг элемента управления при автоматической прокрутке
   void              SetAutoScrollMarginWidth(const int value)       { this.SetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_W,value);  }
   void              SetAutoScrollMarginHeight(const int value)      { this.SetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_H,value);  }
   void              SetAutoScrollMarginAll(const int value)
                       {
                        this.SetAutoScrollMarginWidth(value); this.SetAutoScrollMarginHeight(value);
                       }
   void              SetAutoScrollMargin(const int width,const int height)
                       {
                        this.SetAutoScrollMarginWidth(width); this.SetAutoScrollMarginHeight(height);
                       }
//--- Возвращает (1) ширину, (2) высоту поля вокруг элемента управления при автоматической прокрутке
   int               AutoScrollMarginWidth(void)               const { return (int)this.GetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_W); }
   int               AutoScrollMarginHeight(void)              const { return (int)this.GetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_H); }
  
//--- (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.SetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE,mode);
                        if(prev!=this.AutoSizeMode() && this.ElementsTotal()>0)
                           this.AutoSizeProcess(redraw);
                       }
   ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void)   const { return (ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)this.GetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE); }
   
//--- (1) Устанавливает, (2) возвращает режим привязки границ элемента к контейнеру


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

//+------------------------------------------------------------------+
//| Создаёт новый графический объект                                 |
//+------------------------------------------------------------------+
CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                       const int obj_num,
                                       const string obj_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)
  {
   string name=this.CreateNameDependentObject(obj_name);
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT :
         element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity);
        break;
      case GRAPH_ELEMENT_TYPE_FORM :
         element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL :
         element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL :
         element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
   return element;
  }
//+------------------------------------------------------------------+

Думаю, здесь всё наглядно и просто, и в пояснениях не нуждается.


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

//+------------------------------------------------------------------+
//| Создаёт новый присоединённый элемент                             |
//+------------------------------------------------------------------+
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)
  {
//--- Если тип объекта - меньше, чем базовый WinForms-объект
   if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE)
     {
      //--- сообщаем об ошибке и возвращаем false
      CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE);
      return false;
     }
//--- Если не удалось создать новый графический элемент - возвращаем false
   CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,main,x,y,w,h,colour,opacity,activity);
   if(obj==NULL)
      return false;
//--- Устанавливаем созданному объекту цвет текста как у базовой панели
   obj.SetForeColor(this.ForeColor());
//--- Если тип объекта - Панель
   if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_PANEL)
     {
      //--- устанавливаем цвет рамки равным цвету фона 
      obj.SetColorFrame(obj.ColorBackground());
     }
//--- Если тип объекта - Текстовая метка
   if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_LABEL)
     {
      //--- устанавливаем цвет текста объекта в зависимости от переданного в метод -
      //--- либо цвет текста панели, либо переданный в метод, и цвет рамки, равный цвету текста 
      obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour);
      obj.SetColorFrame(main!=NULL ? main.ColorBackground() : obj.ForeColor());
     }
//--- Если у панели включено автоизменение размеров и есть привязанные объекты - вызываем метод изменения размеров
   if(this.AutoSize() && this.ElementsTotal()>0)
      this.AutoSizeProcess(redraw);
//--- Перерисовываем панель и все добавленные объекты и возвращаем true
   this.Redraw(redraw);
   return true;
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Возвращает список прикреплённых объектов                         |
//| с указанным типом WinForms объекта                               |
//+------------------------------------------------------------------+
CArrayObj *CPanel::GetListWinFormsObjByType(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   return CSelect::ByGraphCanvElementProperty(this.GetListElements(),CANV_ELEMENT_PROP_TYPE,type,EQUAL);
  }
//+------------------------------------------------------------------+

Здесь мы просто возвращаем список, полученный в классе CSelect с указанным типом WinForms-объекта. Если список получить не удалось, или нет объектов с указанным типом, то метод вернёт NULL.


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

//+------------------------------------------------------------------+
//| Возвращает указатель на прикреплённый WinForms-объект            |
//| с указанным типом по индексу                                     |
//+------------------------------------------------------------------+
CWinFormBase *CPanel::GetWinFormsObj(const ENUM_GRAPH_ELEMENT_TYPE type,const int index)
  {
   CArrayObj *list=this.GetListWinFormsObjByType(type);
   return(list!=NULL ? list.At(index) : NULL);
  }
//+------------------------------------------------------------------+

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

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


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

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

Так как в имени всех графических объектов присутствует имя программы, а для создания ресурса графического объекта в методе Create() класса CCanvas используется значение идентификатора графика + количество тиков процессора, прошедших с момента запуска программы, + псевдослучайное число:

//+------------------------------------------------------------------+
//| Create dynamic resource                                          |
//+------------------------------------------------------------------+
bool CCanvas::Create(const string name,const int width,const int height,ENUM_COLOR_FORMAT clrfmt)
  {
   Destroy();
//--- prepare data array
   if(width>0 && height>0 && ArrayResize(m_pixels,width*height)>0)
     {
      //--- generate resource name
      m_rcname="::"+name+(string)ChartID()+(string)(GetTickCount()+MathRand());
      //--- initialize data with zeros
      ArrayInitialize(m_pixels,0);
      //--- create dynamic resource
      if(ResourceCreate(m_rcname,m_pixels,width,height,0,0,0,clrfmt))
        {
         //--- successfully created
         //--- complete initialization
         m_width =width;
         m_height=height;
         m_format=clrfmt;
         //--- succeed
         return(true);
        }
     }
//--- error - destroy object
   Destroy();
   return(false);
  }
//+------------------------------------------------------------------+

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

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

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

//+------------------------------------------------------------------+
//|                                                     TstDE107.mq5 |
//|                                  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"
//--- 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
  };
enum ENUM_BORDER_STYLE
  {
   BORDER_STYLE_NONE=FRAME_STYLE_NONE,                                  // None
   BORDER_STYLE_SIMPLE=FRAME_STYLE_SIMPLE,                              // Simple
   BORDER_STYLE_FLAT=FRAME_STYLE_FLAT,                                  // Flat
   BORDER_STYLE_BEVEL=FRAME_STYLE_BEVEL,                                // Embossed (bevel)
   BORDER_STYLE_STAMP=FRAME_STYLE_STAMP,                                // Embossed (stamp)
  };
#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   // Увеличение и уменьшение
  };
enum ENUM_BORDER_STYLE
  {
   BORDER_STYLE_NONE=FRAME_STYLE_NONE,                                  // Нет рамки
   BORDER_STYLE_SIMPLE=FRAME_STYLE_SIMPLE,                              // Простая рамка
   BORDER_STYLE_FLAT=FRAME_STYLE_FLAT,                                  // Плоская рамка
   BORDER_STYLE_BEVEL=FRAME_STYLE_BEVEL,                                // Рельефная (выпуклая)
   BORDER_STYLE_STAMP=FRAME_STYLE_STAMP,                                // Рельефная (вдавленная)
  };
#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
sinput   ENUM_BORDER_STYLE    InpFrameStyle     =  BORDER_STYLE_NONE;   // Label border style
sinput   ENUM_ANCHOR_POINT    InpTextAlign      =  ANCHOR_LEFT_UPPER;   // Label text align
//--- global variables
CEngine        engine;
color          array_clr[];
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Установка глобальных переменных советника
   ArrayResize(array_clr,2);        // Массив цветов градиентной заливки
   array_clr[0]=C'26,100,128';      // Исходный ≈Тёмно-лазурный цвет
   array_clr[1]=C'35,133,169';      // Осветлённый исходный цвет
//--- Создадим массив с текущим символом и установим его для использования в библиотеке
   string array[1]={Symbol()};
   engine.SetUsedSymbols(array);
   //--- Создадим объект-таймсерию для текущего символа и периода и выведем его описание в журнал
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(false); // Краткие описания
//--- Создадим объект WinForms Panel
   CPanel *pnl=NULL;
   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,90,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));
            obj.SetForeColor(clrRed);
            //--- Рассчитаем ширину и высоту будущего объекта-текстовой метки
            int w=obj.Width()-obj.FrameWidthLeft()-obj.FrameWidthRight()-4;
            int h=obj.Height()-obj.FrameWidthTop()-obj.FrameWidthBottom()-4;
            //--- Создадим объект-текстовую метку
            obj.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LABEL,pnl,2,2,w,h,clrNONE,255,false,false);
            //--- Получаем указатель на вновь созданный объект
            CLabel *lbl=obj.GetElement(0);
            if(lbl!=NULL)
              {
               //--- Если объект имеет чётный, или нулевой индекс в списке - зададим ему цвет текста по умолчанию
               if(i % 2==0)
                  lbl.SetForeColor(CLR_DEF_FORE_COLOR);
               //--- Если индекс объекта в списке нечётный - зададим объекту непрозрачность 127
               else
                  lbl.SetForeColorOpacity(127);
               //--- Укажем для шрифта тип толщины "Black" и
               //--- укажем выравнивание текста из настроек советника
               lbl.SetFontBoldType(FW_TYPE_BLACK);
               lbl.SetTextAlign(InpTextAlign);
               //--- Для объекта с чётным, или нулевым индексом укажем для текста цену Bid, иначе - цену Ask символа 
               lbl.SetText(GetPrice(i % 2==0 ? SYMBOL_BID : SYMBOL_ASK));
               //--- Укажем толщину и тип рамки для текстовой метки и обновим модифицированный объект
               lbl.SetFrameWidthAll(1);
               lbl.SetBorderStyle((ENUM_FRAME_STYLE)InpFrameStyle);
               lbl.Update(true);
              }
           }
        }
      //--- Перерисуем все объекты в порядке их иерархии
      pnl.Redraw(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Вся логика здесь подробно прокомментирована в коде. Перед созданием объектов-текстовых меток, установим для панели, к которой будет прикрепляться метка, цвет фона текста. Этот цвет должен унаследовать объект-текстовая метка. Вот тут мы это и проверим. А далее будем уже в самом текстовом объекте менять его цвет по мере необходимости.

В обработчике OnTick() будем вписывать в каждую текстовую метку значения цен Ask и Bid. Для объектов с чётным индексом в списке будем вписывать цену Bid, а для нечётных — цену Ask:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   //--- Получим указатель на объект-панель по имени
   CPanel *pnl=engine.GetWFPanel("WFPanel");
   if(pnl!=NULL)
     {
      //--- Получим список всех привязанных объектов-панелей
      CArrayObj *list=pnl.GetListWinFormsObjByType(GRAPH_ELEMENT_TYPE_WF_PANEL);
      //--- В цикле по привязанным объектам-панелям
      for(int i=0;i<list.Total();i++)
        {
         //--- получим указатель на очередной объект-панель
         CPanel *obj=pnl.GetWinFormsObj(GRAPH_ELEMENT_TYPE_WF_PANEL,i);
         if(obj!=NULL)
           {
            //--- у полученного объекта возьмём указатель на первый (и единственный) объект-текстовую метку
            CLabel *lbl=obj.GetWinFormsObj(GRAPH_ELEMENT_TYPE_WF_LABEL,0);
            if(lbl!=NULL)
              {
               //--- установим новый текст для объекта и перерисуем его
               lbl.SetText(GetPrice(i % 2==0 ? SYMBOL_BID : SYMBOL_ASK));
               lbl.Redraw(false);
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

В самом конце листинга советника впишем функцию, возвращающую строковое значение цены Bid или Ask:

//+------------------------------------------------------------------+
//| Возвращает строковое значение Bid/Ask                            |
//+------------------------------------------------------------------+
string GetPrice(const ENUM_SYMBOL_INFO_DOUBLE price)
  {
   return((price==SYMBOL_ASK ? "Ask: " : "Bid: ")+DoubleToString(SymbolInfoDouble(Symbol(),price),Digits()));
  }
//+------------------------------------------------------------------+

В зависимости от переданного в функцию значения выбираем что написать перед значением цены — "Ask" или "Bid" и прибавляем к этому тексту строковое значение цены с указанным её типом.

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


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

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


Что дальше

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

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

К содержанию

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

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