Изучение Стандартной библиотеки MQL5 (Часть 1): Интеграция и связывание компонентов
Содержание
Введение
Многие разработчики, входящие в экосистему MQL5 — от увлеченных новичков до опытных программистов, перешедших из других языков, — сталкиваются с распространенной и неприятной проблемой: они умеют писать код, но им трудно создавать надежные советники профессионального уровня. Стандартная библиотека MQL5 — это мощный набор предварительно созданных, протестированных и оптимизированных классов, разработанных для устранения этой проблемы. Однако само её богатство может показаться чрезмерным. Настоящая проблема заключается не в недостатке компонентов, а в отсутствии четкой дорожной карты — практического руководства по интеграции этих сложных строительных блоков в целостную, функционирующую торговую систему.
Разработчики часто оказываются в ситуации, когда у них есть целый набор высокоточных инструментов, но нет схемы, как собрать из них готовое решение. Чтобы упростить эту сложность, мы воспользуемся аналогией из реальной жизни: подобно тому, как человеческий организм полагается на специализированные органы, работающие под координацией головного мозга, советник полагается на специализированные модули, координируемые его основной программой.
В этой аналогии советник выступает в роли мозга — центрального органа, принимающего решения. Вспомогательные классы библиотеки играют роль его органов, выполняя специализированные функции:
- Класс CTrade совершает сделки — подобно рукам, выполняющим действия.
- Класс CIndicators анализирует рыночные данные — подобно глазам, воспринимающим окружающую среду.
- Класс CAccountInfo отслеживает состояние счета — подобно нервной системе, собирающей и передающей обратную связь.

Рис. 1. Концептуальная программная система

Рис. 2. Концептуальная биологическая система
Эта аналогия носит исключительно иллюстративный характер, призванный сделать абстрактную структуру объектно-ориентированного программирования (ООП) более интуитивно понятной, помогая читателям визуализировать, как каждая часть библиотеки взаимодействует, формируя скоординированную и эффективную торговую систему.
Все это — преимущества объектно-ориентированного программирования: модульность, возможность повторного использования и абстракция, — но в этой серии статей мы намеренно пропускаем подробное теоретическое объяснение, чтобы сначала сосредоточиться на практическом применении. Многие учащиеся лучше понимают концепции на практике, и как только вы начинаете создавать и интегрировать классы на практике, основные принципы ООП начинают обретать естественный смысл.
Для читателей, которые предпочитают изучить теорию ООП перед продолжением, существует множество отличных источников на форуме сообщества MQL5 и за его пределами, которые подробно объясняют инкапсуляцию, наследование и полиморфизм. Однако наш подход в этой статье основан на практике: на реальных примерах, диаграммах и аналогиях, которые сделают ваше знакомство со стандартной библиотекой MQL5 приятным и продуктивным.
К концу этого обсуждения мы выйдем за рамки отдельных примеров и представим генеральный план — исчерпывающее руководство по вплетению мощных элементов стандартной библиотеки MQL5 в прочную структуру профессионального советника, а также откроем способы расширения самой библиотеки.

Рис. 3: Советник как мозг, а классы как органы
Хотя советник справедливо рассматривается как мозг или основная программа, мы также должны понимать, что сами модули могут быть построены путем включения других модулей — то, что мы называем зависимостями. Точно так же, как мы учимся интегрировать целые библиотеки в программы советников, нам необходимы те же знания при связывании модулей или встраивании одного модуля в другой.
Приведенная выше аналогия показывает, что взаимосвязи между модулями существуют двумя ключевыми способами: как правило, через координацию основной программы, но также и через прямые зависимости. Например, класс CPanel не существует сам по себе — он напрямую включает в себя и зависит от WndObj.mqh и ChartObjectsTxtControls.mqh.

Рис. 4. Взаимодействие между классами
Хорошая новость в том, что эта сеть взаимоотношений не так уж сложна, если ее понять. При использовании любого модуля в советнике, будь то простой класс или сложный зависимый модуль, существуют последовательные, повторяемые шаги, которые гарантируют успешную интеграцию каждый раз. Смотрите фрагмент кода ниже — он выделяет ключевые зависимости, используемые Panel.mqh, и места их включения.
//+------------------------------------------------------------------+ //| Panel.mqh | //| Copyright 2000-2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include "WndObj.mqh" #include <ChartObjects\ChartObjectsTxtControls.mqh>
В следующем разделе я расскажу о шагах, которые обычно выполняю при интеграции библиотечных классов в свои проекты. Это поможет вам сформировать прочное понимание, прежде чем мы перейдем к этапу реализации, где мы более подробно рассмотрим практические примеры.
Интеграция какого-либо класса в советник
В большинстве случаев выбор классов для использования происходит после определения общей стратегии или цели вашего советника — нет необходимости перегружать проект ненужными компонентами. Однако в этом разделе мы сосредоточимся не на разработке стратегии, а на изучении самого процесса интеграции.
Наша цель — поделиться необходимыми знаниями и практическими шагами, которые позволят вам уверенно интегрировать любой библиотечный модуль в свои будущие проекты, когда возникнет такая необходимость. В следующем разделе описаны эти шаги, сопровождаемые структурной схемой для иллюстрации процесса.
Последовательность операций:
- Анализ – изучение цели класса и цепочки наследования
- Включение - добавление директивы #include с указанием корректного пути
- Объявление — создание экземпляра объекта в советнике
- Инициализация — вызов Create() в OnInit() с параметрами
- Во время выполнения — обновление в OnTick(), обработка событий
- Очистка - освобождение ресурсов в OnDeinit()
Обработка ошибок:
- Ошибки компиляции → Проверка зависимостей
- Сбои создания → Проверка параметров
- Проблемы во время работы → Мониторинг состояния объекта

Рис. 5. План интеграции библиотеки MQL5
Процесс интеграции модулей
Анализ зависимостей
- Связи родитель-потомок: например, CPanel → WndObj → ChartObjectsTxtControls
- Контракты интерфейсов: какие методы должны быть реализованы
- Поток данных: как информация проходит между модулями

Рис. 6. Иерархия наследования модулей
Этапы реализации
- Цепочка включений: каждый модуль включает свои прямые зависимости
- Настройка наследования: иерархии классов устанавливаются через наследование
- Реализация методов: переопределение виртуальных методов по мере необходимости
- Изолированное тестирование: тестирование модулей независимо друг от друга перед интеграцией советника
Общие паттерны
- Многоуровневая архитектура: модули верхнего уровня зависят от модулей нижнего уровня
- Разделение интерфейсов: модули предоставляют чёткие и специализированные API
- Внедрение зависимостей: передавайте необходимые объекты вместо жесткого кодирования

Рис. 7. Процесс интеграции модулей
После того как архитектурная основа была прочно заложена, а план интеграции четко определен, мы переходим к этапу реализации, где теория воплощается в практику. Именно здесь мы применяем структурированный процесс интеграции к работающему проекту, превращая концептуальное понимание в работающий советник.
Наш план реализации заключается в создании советника InfoPanel EA — информационной панели, демонстрирующей, как интегрировать класс CPanel и его зависимости в торговую среду. Этот пример покажет, как именно применять каждый этап нашей методологии интеграции для создания чистой, профессиональной и расширяемой системы.
Реализация
При работе с любым библиотечным классом в MQL5 интеграция начинается с понимания того, что представляет собой этот класс на самом деле — его назначение, его происхождение и то, как он вписывается в экосистему элементов управления на графике. CPanel — это больше, чем просто прямоугольная область; это самодостаточный интерфейсный слой, который наследует от CWndObj и управляет собственным жизненным циклом на графике. Он служит архитектурным каркасом для сложных компонентов пользовательского интерфейса. Прежде чем какой-либо код появится в советнике, это понимание формирует концептуальную основу для бесшовной интеграции.
Анализ класса CPanel — в Panel.mqh
Первое знакомство с классом всегда должно носить исследовательский характер. Изучение заголовка Panel.mqh позволяет выявить ключевые взаимосвязи и точки расширения. Класс наследует от CWndObj, что означает, что он наследует способность обрабатывать события и управлять оконными объектами на графике. Внутри он инкапсулирует CChartObjectRectLabel, графический прямоугольник, который задает видимую рамку панели.
Определив его общедоступные точки входа — в частности, Create() и BorderType() — вы определите ключевые точки управления, с которыми будет взаимодействовать ваш советник. Такой подход заменяет метод проб и ошибок обоснованным проектированием.
//+------------------------------------------------------------------+ //| Panel.mqh | //| Copyright 2000-2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include "WndObj.mqh" #include <ChartObjects\ChartObjectsTxtControls.mqh> //+------------------------------------------------------------------+ //| Class CPanel | //| Usage: control that is displayed by | //| the CChartObjectRectLabel object | //+------------------------------------------------------------------+ class CPanel : public CWndObj { private: CChartObjectRectLabel m_rectangle; // chart object //--- parameters of the chart object ENUM_BORDER_TYPE m_border; // border type public: CPanel(void); ~CPanel(void); //--- create virtual bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2); //--- parameters of the chart object ENUM_BORDER_TYPE BorderType(void) const { return(m_border); } bool BorderType(const ENUM_BORDER_TYPE type); protected: //--- handlers of object settings virtual bool OnSetText(void) { return(m_rectangle.Description(m_text)); } virtual bool OnSetColorBackground(void) { return(m_rectangle.BackColor(m_color_background)); } virtual bool OnSetColorBorder(void) { return(m_rectangle.Color(m_color_border)); } //--- internal event handlers virtual bool OnCreate(void); virtual bool OnShow(void); virtual bool OnHide(void); virtual bool OnMove(void); virtual bool OnResize(void); virtual bool OnChange(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CPanel::CPanel(void) : m_border(BORDER_FLAT) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CPanel::~CPanel(void) { } //+------------------------------------------------------------------+ //| Create a control | //+------------------------------------------------------------------+ bool CPanel::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2) { //--- call method of the parent class if(!CWndObj::Create(chart,name,subwin,x1,y1,x2,y2)) return(false); //--- create the chart object if(!m_rectangle.Create(chart,name,subwin,x1,y1,Width(),Height())) return(false); //--- call the settings handler return(OnChange()); } //+------------------------------------------------------------------+ //| Set border type | //+------------------------------------------------------------------+ bool CPanel::BorderType(const ENUM_BORDER_TYPE type) { //--- save new value of parameter m_border=type; //--- set up the chart object return(m_rectangle.BorderType(type)); } //+------------------------------------------------------------------+ //| Create object on chart | //+------------------------------------------------------------------+ bool CPanel::OnCreate(void) { //--- create the chart object by previously set parameters return(m_rectangle.Create(m_chart_id,m_name,m_subwin,m_rect.left,m_rect.top,m_rect.Width(),m_rect.Height())); } //+------------------------------------------------------------------+ //| Display object on chart | //+------------------------------------------------------------------+ bool CPanel::OnShow(void) { return(m_rectangle.Timeframes(OBJ_ALL_PERIODS)); } //+------------------------------------------------------------------+ //| Hide object from chart | //+------------------------------------------------------------------+ bool CPanel::OnHide(void) { return(m_rectangle.Timeframes(OBJ_NO_PERIODS)); } //+------------------------------------------------------------------+ //| Absolute movement of the chart object | //+------------------------------------------------------------------+ bool CPanel::OnMove(void) { //--- position the chart object return(m_rectangle.X_Distance(m_rect.left) && m_rectangle.Y_Distance(m_rect.top)); } //+------------------------------------------------------------------+ //| Resize the chart object | //+------------------------------------------------------------------+ bool CPanel::OnResize(void) { //--- resize the chart object return(m_rectangle.X_Size(m_rect.Width()) && m_rectangle.Y_Size(m_rect.Height())); } //+------------------------------------------------------------------+ //| Set up the chart object | //+------------------------------------------------------------------+ bool CPanel::OnChange(void) { //--- set up the chart object return(CWndObj::OnChange() && m_rectangle.BorderType(m_border)); } //+------------------------------------------------------------------+
Позже каждая из этих строк напрямую отразится в соответствующем шаге инициализации советника.
Этапы интеграции
Этап 1. Включение заголовка и зависимостей
Как только цель становится ясна, следующим шагом является включение. Советник должен получить доступ к CPanel и её базовым классам с помощью соответствующих директив #include. Структура файлов MQL5 строго относится к путям включения, поэтому всегда проверяйте, что ваш файл Panel.mqh и поддерживающие его заголовочные файлы (WndObj.mqh, ChartObjectsTxtControls.mqh и т. д.) доступны в каталоге Include проекта или глобальной директории.
#include <Controls\Panel.mqh> // gives access to CPanel and its internals
На этом этапе советник “знает”, что класс панели существует — он готов к созданию экземпляра.
Этап 2. Объявление объекта
Объявление определяет область видимости и время жизни панели в советнике. Большинство разработчиков предпочитают объявлять CPanel как указатель на глобальном уровне, чтобы объект сохранялся между вызовами обработчиков событий и мог быть корректно удалён на этапе очистки.
CPanel *mainPanel = NULL; Это делает объект доступным во всех обработчиках событий (OnInit, OnTick, OnDeinit и т. д.), обеспечивая согласованный жизненный цикл.
Этап 3. Инициализация — ввод панели в работу
Создание происходит внутри OnInit(), и именно здесь дизайн заголовка встречается с выполнением. Create() требует шесть аргументов: идентификатор графика, уникальное имя, целевое подокно и четыре координаты, определяющие прямоугольник панели. Всегда проверяйте, что вызов возвращает true; результат false часто указывает на проблему с параметром или зависимостью.
int OnInit() { mainPanel = new CPanel(); if(!mainPanel.Create(ChartID(), "InfoPanel", 0, 10, 10, 400, 120)) { Print("Panel creation failed."); delete mainPanel; mainPanel = NULL; return(INIT_FAILED); } mainPanel.BorderType(BORDER_RAISED); mainPanel.Text("Information Panel"); return(INIT_SUCCEEDED); }
Этот фрагмент демонстрирует минимальную, но полную инициализацию. Теперь панель визуально существует на графике, занимая указанный прямоугольник.
Этап 4. Управление во время выполнения
Сама панель может не нуждаться в постоянных обновлениях, если она статична, но именно здесь расширяется ваша логика интеграции. Возможно, вы захотите встроить дочерние элементы управления, такие как метки или кнопки, или периодически обновлять содержимое панели.
Для динамических обновлений событие OnTimer() обеспечивает стабильный механизм обновления, не перегружая цикл OnTick().
void OnTimer() { // Placeholder for runtime UI updates }
Каждое обновление может обновлять данные, отображаемые внутри панели, без перерисовки объекта.
Этап 5. Очистка и обработка ошибок
Для предотвращения утечек памяти необходимо уничтожать каждый динамически созданный объект. В OnDeinit() вызовите оператор delete и установите указатель в NULL. Это обеспечит корректное освобождение памяти и предотвратит появление висячих объектов графика.
void OnDeinit(const int reason) { if(mainPanel) { mainPanel.OnHide(); delete mainPanel; mainPanel = NULL; } }
Если компиляция завершится неудачей, проверьте отсутствующие файлы типа include или неопределенные ссылки. Ошибки создания обычно связаны с некорректными координатами или конфликтами имен, в то время как проблемы с мерцанием во время работы или событиями часто возникают из-за перекрывающихся объектов графика.
Анализ CChartObjectText в ChartObjectsTxtControls.mqh
В этом разделе мы сосредоточимся на интерфейсном слое заголовка ChartObjectsTxtControls.mqh. Для ясности и краткости мы выделили только публичную и верхнеуровневую часть класса — части, определяющие способ взаимодействия с ним. Полный исходный код намного длиннее, но для нашего анализа нам нужен только публичный интерфейс, который представляет собой “входную дверь” класса.
Такой выборочный подход является преднамеренным. Суть объектно-ориентированного программирования (ООП) — это абстракция: нам не нужно знать, как работает внутреннее устройство, если мы понимаем интерфейс и как правильно его использовать. Интерфейс определяет доступные методы, их параметры и ожидаемое поведение — этого достаточно для эффективного построения интеграций.
Наша цель здесь — проанализировать заголовок, чтобы понять его назначение и определить ключевые точки взаимодействия — методы, которые мы будем вызывать из нашего основного советника для описания или манипулирования текстом на графике. В данном случае мы включили этот заголовок не для построения панели (как когда он служил зависимостью CPanel), а исключительно для текстового описания в нашем советнике.
Когда он ранее использовался в качестве зависимости, нам не нужно было особо сосредотачиваться на его внутренней логике — это делала родительская панель CPanel. Однако в этом образовательном контексте мы целенаправленно изучаем его, чтобы понять, как построен каждый класс и как использовать его независимо.
Приведённый ниже фрагмент показывает интерфейс CChartObjectText, который предоставляет все необходимые методы для работы с текстовыми объектами непосредственно на графике. Среди них — Create(), FontSize() и Anchor() — наши ключевые точки взаимодействия при интеграции текстовых элементов в панель или советник. Это методы, позволяющие задавать положение пикселей, устанавливать стили шрифтов, управлять выравниванием и видимостью — всё, что нужно для формирования текстовой части нашей панели InfoPanel.
//+------------------------------------------------------------------+ //| ChartObjectsTxtControls.mqh | //| Copyright 2000-2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ //| All text objects. | //+------------------------------------------------------------------+ #include "ChartObject.mqh" //+------------------------------------------------------------------+ //| Class CChartObjectText. | //| Purpose: Class of the "Text" object of chart. | //| Derives from class CChartObject. | //+------------------------------------------------------------------+ class CChartObjectText : public CChartObject { public: CChartObjectText(void); ~CChartObjectText(void); //--- method of creating the object bool Create(long chart_id,const string name,const int window,const datetime time,const double price); //--- method of identifying the object virtual int Type(void) const override { return(OBJ_TEXT); } //--- methods of access to properties of the object double Angle(void) const; bool Angle(const double angle) const; string Font(void) const; bool Font(const string font) const; int FontSize(void) const; bool FontSize(const int size) const; ENUM_ANCHOR_POINT Anchor(void) const; bool Anchor(const ENUM_ANCHOR_POINT anchor) const; //--- methods for working with files virtual bool Save(const int file_handle) override; virtual bool Load(const int file_handle) override; };
Этапы интеграции CChartObjectText в советник
Этап 1. Включение
Мы размещаем файл ChartObjectsTxtControls.mqh в верхней части советника, чтобы определения классов были видны. Мы используем тот же путь, что и библиотека.Если вы уже включили Panel.mqh, этот заголовочный файл часто подтягивается, но явное его включение в советник делает намерения и зависимости понятными.
#include <ChartObjects\ChartObjectsTxtControls.mqh> // provides CChartObjectLabel et al.
Этап 2. Объявление
Мы объявляем объекты меток там, где функции жизненного цикла советника могут получить к ним доступ — в глобальной области видимости. Используйте объекты стека для классов текста графика (они управляют прикреплением внутри) или указатели, если вы предпочитаете семантику создания/удаления. Тщательно называйте метки (уникальные для каждого графика). Если вы ожидаете несколько меток, используйте понятные паттерны именования: "InfoPanel_123_txt1".
// global declarations CChartObjectLabel txtDesc; // pixel-based label (good for panel body)
Этап 3. Инициализация (OnInit)
Далее мы создаем метку внутри OnInit(), используя сигнатуру, показанную в заголовочном файле ChartObjectsTxtControls.mqh: Create(long chart_id, const string name, const int window, const int X, const int Y). Размещаем её относительно области панели, чтобы обеспечить согласованность компоновки.
Если создание метки не удается, запишите в лог полезную информацию (идентификатор графика, название, координаты) и прервите инициализацию.
int OnInit() { string txt_name = panel_name + "_txtDesc"; int left = PANEL_X1 + PADDING; int top = PANEL_Y1 + PADDING + 18; if(!txtDesc.Create(ChartID(), txt_name, 0, left, top)) { Print("txtDesc.Create failed"); return(INIT_FAILED); } txtDesc.Description("Hello trader! I am InfoPanel EA"); txtDesc.FontSize(11); // txtDesc.Anchor(ANCHOR_LEFT_UPPER); // if API available return(INIT_SUCCEEDED); }
Этап 4. Во время выполнения (OnTick/OnTimer/OnChartEvent)
Сведите к минимуму объём работы во время выполнения для статического текста. Если вам нужны динамические поля, обновляйте Description() из OnTimer() (устанавливайте EventSetTimer() в OnInit()), а не каждый тик. Если вы добавляете интерактивность (кнопки или редактирование из того же заголовочного файла), перенаправьте OnChartEvent() и проверьте sparam на наличие кликов по объектам.
Для нескольких строк используйте либо несколько экземпляров CChartObjectLabel, расположенных вертикально друг над другом, либо сгенерируйте \n и проверьте, принимает ли Description() переносы строк; если нет, предпочтительнее использовать отдельные метки.
void OnTimer() { // e.g., update price line inside the panel txtDesc.Description("Bid: " + DoubleToString(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits)); }
Этап 5. Очистка (OnDeinit)
Удалите объект графика при завершении работы советника. Используйте метод класса Delete() (или ObjectDelete() в качестве резервного варианта). Не вызывайте внутренние защищенные методы других классов — просто удалите свою метку и освободите любой указатель панели.
void OnDeinit(const int reason) { txtDesc.Delete(); // removes the chart object // delete panel pointer here if allocated EventKillTimer(); // if used }
Соединение всех частей в советник
Окончательная интеграция класса CPanel в наш советник InfoPanel завершает базовый этап нашего исследования стандартной библиотеки. Эта панель не является интерактивным дашбордом или контроллером торговых операций — это статическое отображение информации, простое, но профессионально оформленное информационное отображение на графике, которое наглядно показывает, какой продукт используется. Многие разработчики могли бы выбрать аналогичный дизайн, чтобы отображать идентификацию и подробности своего советника непосредственно на графике, избегая ограничений, связанных с именем графика по умолчанию, или необходимости копаться в свойствах. Это ненавязчивая, но практичная визуальная подпись — визуальный тег, который делает рабочее пространство графика более понятным.
Хотя концепция может показаться скромной, ее значимость заключается в методе, а не в масштабе. Здесь мы отработали полный цикл интеграции классов: от включения заголовка стандартной библиотеки до создания и настройки экземпляра CPanel и дополнения её текстовыми элементами графика для осмысленного отображения. Этот процесс отражает саму суть того, как структурируются и управляются профессиональные компоненты MQL5. Речь идет о понимании архитектурного ритма — включение, объявление, создание и управление — который вы впоследствии будете использовать для гораздо более сложных систем.
Этот первый проект намеренно прост, потому что он закладывает основу для дальнейшего расширения. Как только вы освоите CPanel, добавление других элементов, таких как CChartObjectLabel для более насыщенного текстового вывода или даже CPicture для брендинга, станет естественным. Вместе эти статические элементы образуют небольшую, но полноценную экосистему визуализации информации, готовую к дальнейшему развитию по мере продвижения нашего исследования.
Более широкая цель состоит в том, чтобы приучить себя мыслить модульно, рассматривать стандартную библиотеку не как жесткую структуру, а как гибкую основу, которую можно расширять бесконечно. Каждый класс — CPanel, CChartObjectLabel, CPicture и другие — является строительным блоком. То, что сегодня начинается как простая панель отображения, впоследствии может превратиться в комплексное рабочее пространство для визуализации данных, настраиваемых элементов управления и адаптивных компоновок.
Поэтому наш путь с CPanel — это не просто создание статической информационной панели; это начало освоения логики и мастерства, необходимых для построения целых систем, начиная со стандартной библиотеки — шаг за шагом, слой за слоем.
//+------------------------------------------------------------------+ //| InfoPanel_EA.mq5 | //| Minimal example: creating a CPanel and a label text showing | //| a short EA description with panel background & border. We will | //| also add a logo to explore CPicture. | //+------------------------------------------------------------------+ #property copyright "Clemence Benjamin" #property version "1.0" #include <Controls/Panel.mqh> // Panel header (correct path) #include <ChartObjects\ChartObjectsTxtControls.mqh> // for CChartObjectLabel //---- Globals -------------------------------------------------------- CPanel *infoPanel = NULL; // pointer to panel (allocate/free) CChartObjectLabel txtDesc; // label text inside the panel // layout (pixels) int PANEL_X1 = 10; int PANEL_Y1 = 30; int PANEL_X2 = 300; // widened to give text room int PANEL_Y2 = 100; int PADDING = 10; // short description that fits comfortably on one line string EA_DESCRIPTION = "Hello trader! I am InfoPanel EA"; //+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { // allocate the panel object infoPanel = new CPanel(); if(infoPanel == NULL) { Print("InfoPanel: allocation failed"); return(INIT_FAILED); } // unique panel name string panel_name = "InfoPanel_" + IntegerToString((int)ChartID()); // create the panel on the current chart (subwindow 0 = main) if(!infoPanel.Create(ChartID(), panel_name, 0, PANEL_X1, PANEL_Y1, PANEL_X2, PANEL_Y2)) { Print("InfoPanel: Create() failed"); delete infoPanel; infoPanel = NULL; return(INIT_FAILED); } // appearance: border type, header text, background and border colors infoPanel.BorderType(BORDER_RAISED); infoPanel.Text("InfoPanel"); // Set a visible background and border color. // Use standard color constants; change them if you prefer another palette. // If your library expects ARGB or different color encoding, adapt accordingly. infoPanel.ColorBackground(clrSilver); // panel background infoPanel.ColorBorder(clrBlack); // panel border // compute child label position inside panel (some padding below header) int left = PANEL_X1 + PADDING; int top = PANEL_Y1 + PADDING + 18; // leave header area // create a CChartObjectLabel as panel body (unique name) string txt_name = panel_name + "_txtDesc"; if(!txtDesc.Create(ChartID(), txt_name, 0, left, top)) { Print("InfoPanel: txtDesc.Create() failed"); delete infoPanel; infoPanel = NULL; return(INIT_FAILED); } // set the short description and font size using the correct API txtDesc.Description(EA_DESCRIPTION); txtDesc.FontSize(11); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // remove the label object txtDesc.Delete(); // free the panel (do NOT call protected methods) if(infoPanel != NULL) { delete infoPanel; infoPanel = NULL; } // safe kill of timer if later added EventKillTimer(); } //+------------------------------------------------------------------+ //| Expert tick function (kept intentionally light) | //+------------------------------------------------------------------+ void OnTick() { // static info panel — no per-tick UI updates } //+------------------------------------------------------------------+ //| Chart event handler (optional extension point) | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { // placeholder for future interactive controls }

Рис. 8. Первоначальное тестирование
Теперь, сделав еще один шаг, мы добавим на панель логотип BMP, чтобы придать ей узнаваемый вид. Я всегда предпочитал использовать инструменты с открытым исходным кодом для создания изображений. GIMP — отличный выбор, поскольку он позволяет быстро и легко экспортировать изображения в формате BMP. Конечно, вы можете использовать любое другое программное обеспечение, с которым вам удобно работать. После того, как ваш логотип создан и сохранен в папке "Изображения" вашего терминала для быстрого доступа, мы можем перейти к изучению класса CPicture и спланировать, как интегрировать его в нашу панель.
Ниже показаны два изображения. На рис. 9 показано, где будет расположен логотип на нашей панели, а на рис. 10 — логотип, который я создал с помощью GIMP. При экспорте изображения в формате BMP убедитесь, что используете 24-битную глубину цвета, один слой и отсутствие альфа-канала. Эти настройки отлично работали во время тестирования — другие конфигурации экспорта приводили к изображениям, которые некорректно отображались на панели. Показанное здесь изображение логотипа не в корректном формате BMP, поэтому оно не будет работать должным образом. Я использовал версию PNG, которая поддерживается для целей публикации. Логотип, прикрепленный к статье, вы найдете внизу.

Рис. 9. Пространство логотипа советника

Рис. 10. Логотип размером 56x59 пикселей
Разбор и подготовка класса CPicture к интеграции
Класс CPicture расширяет основу, заложенную нами ранее с помощью CPanel и CChartObjectLabel. Подобно тому, как панель предоставляла нам контейнер, а метка — текст, этот класс предлагает простой способ отображения растровых изображений на графике — идеально подходит для нашего следующего усовершенствования, где мы хотим добавить миниатюру логотипа на информационную панель.
По своей сути, CPicture представляет собой небольшую обертку над объектом графика CChartObjectBmpLabel. Он наследует от CWndObj, то есть следует тем же методам жизненного цикла (Create, OnShow, OnHide и т. д.), которые мы видели ранее. Такая конструкция гарантирует, что мы сможем интегрировать его в наш советник, используя те же знакомые этапы, что и с предыдущими классами — выделение памяти, создание, настройка и очистка.
При более внимательном рассмотрении выделяются несколько ключевых моментов. Основной метод создания, Create(chart, name, subwin, x1, y1, x2, y2), определяет область объекта на графике в пиксельных координатах, как и в CPanel. После создания мы можем назначить фактическое изображение с помощью BmpName(name), которое указывает на файл .bmp, расположенный в папке Images терминала. Метод Border(value) регулирует видимую ширину или толщину границы области изображения, хотя для корректного отображения логотипа мы, скорее всего, установим это значение равным нулю. Внутри эти два метода обновляют базовый объект графика через обработчик OnChange().
Наши ключевые точки взаимодействия при интеграции
Для достижения своих целей нам нужно сосредоточиться лишь на нескольких ключевых точках взаимодействия:
- Create() – инициализирует объект на графике, используя уникальное имя и пиксельные координаты.
- BmpName() – назначает растровый файл для отображения, например, логотип нашего советника.
- Border() – при необходимости определяет значения границы или ширины (необязательно для логотипов).
- Деструктор или оператор delete — для безопасного освобождения памяти при удалении советника.
//+------------------------------------------------------------------+ //| Picture.mqh | //| Copyright 2000-2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include "WndObj.mqh" #include <ChartObjects\ChartObjectsBmpControls.mqh> //+------------------------------------------------------------------+ //| Class CPicture | //| Note: image displayed by | //| the CChartObjectBmpLabel object | //+------------------------------------------------------------------+ class CPicture : public CWndObj { private: CChartObjectBmpLabel m_picture; // chart object //--- parameters of the chart object int m_border; // border width string m_bmp_name; // filename public: CPicture(void); ~CPicture(void); //--- create virtual bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2); //--- parameters of the chart object int Border(void) const { return(m_border); } bool Border(const int value); string BmpName(void) const { return(m_bmp_name); } bool BmpName(const string name); protected: //--- internal event handlers virtual bool OnCreate(void); virtual bool OnShow(void); virtual bool OnHide(void); virtual bool OnMove(void); virtual bool OnChange(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CPicture::CPicture(void) : m_border(0), m_bmp_name(NULL) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CPicture::~CPicture(void) { } //+------------------------------------------------------------------+ //| Create a control | //+------------------------------------------------------------------+ bool CPicture::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2) { //--- call method of the parent class if(!CWndObj::Create(chart,name,subwin,x1,y1,x2,y2)) return(false); //--- create the chart object if(!m_picture.Create(chart,name,subwin,x1,y1)) return(false); //--- call the settings handler return(OnChange()); } //+------------------------------------------------------------------+ //| Set border width | //+------------------------------------------------------------------+ bool CPicture::Border(const int value) { //--- save new value of parameter m_border=value; //--- set up the chart object return(m_picture.Width(value)); } //+------------------------------------------------------------------+ //| Set image | //+------------------------------------------------------------------+ bool CPicture::BmpName(const string name) { //--- save new value of parameter m_bmp_name=name; //--- set up the chart object return(m_picture.BmpFileOn(name)); } //+------------------------------------------------------------------+ //| Create object on chart | //+------------------------------------------------------------------+ bool CPicture::OnCreate(void) { //--- create the chart object by previously set parameters return(m_picture.Create(m_chart_id,m_name,m_subwin,m_rect.left,m_rect.top)); } //+------------------------------------------------------------------+ //| Display object on chart | //+------------------------------------------------------------------+ bool CPicture::OnShow(void) { return(m_picture.Timeframes(OBJ_ALL_PERIODS)); } //+------------------------------------------------------------------+ //| Hide object from chart | //+------------------------------------------------------------------+ bool CPicture::OnHide(void) { return(m_picture.Timeframes(OBJ_NO_PERIODS)); } //+------------------------------------------------------------------+ //| Absolute movement of the chart object | //+------------------------------------------------------------------+ bool CPicture::OnMove(void) { //--- position the chart object return(m_picture.X_Distance(m_rect.left) && m_picture.Y_Distance(m_rect.top)); } //+------------------------------------------------------------------+ //| Set up the chart object | //+------------------------------------------------------------------+ bool CPicture::OnChange(void) { //--- set up the chart object return(m_picture.Width(m_border) && m_picture.BmpFileOn(m_bmp_name)); } //+------------------------------------------------------------------+
С помощью этих простых хэндлов у нас есть все необходимое для интеграции графического компонента в нашу панель.
Планирование интеграции
Теперь, когда мы понимаем, как работает CPicture, можно спланировать, как добавить его в наш InfoPanel_EA. Файл изображения — наш логотип — сначала должен быть подготовлен в формате BMP. Как упоминалось ранее, такие инструменты, как GIMP, упрощают экспорт в BMP. После того, как у нас есть файл, мы помещаем его в папку Images терминала (MQL5/Images/), чтобы советник мог легко получить к нему доступ по имени.
В своем коде мы:
- Включим заголовочный файл Picture.mqh.
- Объявим глобальный указатель для нашего экземпляра CPicture, как мы это делали для панели.
- Создадим объект в OnInit(), аккуратно расположив его внутри или непосредственно над панелью.
- Укажем имя изображения с помощью BmpName("logo.bmp"), чтобы загрузить выбранный файл.
- Освободим ресурсы в OnDeinit(), чтобы обеспечить корректное завершение работы.
Таким образом, наша панель превратится из простого текстового отображения в область с фирменной информацией — тонкий, но значимый шаг, который делает презентацию советника более профессиональной и узнаваемой.
Обоснование этого этапа
Хотя это дополнение может показаться незначительным, оно углубляет наше понимание того, как любой визуальный класс из стандартной библиотеки может быть объединен на практике. Каждая интеграция подкрепляет паттерн: безопасное создание, настройка и управление жизненным циклом. К моменту завершения интеграции логотипа мы объединим три различных стандартных класса — CPanel, CChartObjectLabel и CPicture — в один целостный интерфейс.
Это подготовит нас к следующему разделу, где мы запустим полную интеграцию и разместим логотип нашего советника на самой панели. Это завершит создание нашей статической информационной панели и послужит визуальным подтверждением всего, что мы изучили до сих пор. В следующем разделе будет продемонстрировано, как эти классы могут гармонично сосуществовать, делая наш советник одновременно информативным и визуально привлекательным.
//+------------------------------------------------------------------+ //| InfoPanel_EA.mq5 | //| Info panel with logo placed inside the panel and on foreground | //+------------------------------------------------------------------+ #property copyright "Clemence Benjamin" #property version "1.0" #include <Controls\Panel.mqh> #include <ChartObjects\ChartObjectsTxtControls.mqh> #include <Controls\Picture.mqh> //---- Globals -------------------------------------------------------- CPanel *infoPanel = NULL; CChartObjectLabel txtDesc; CPicture *logo = NULL; // layout (pixels) int PANEL_X1 = 10; int PANEL_Y1 = 30; int PANEL_X2 = 300; int PANEL_Y2 = 100; int PADDING = 10; // logo size and filename int LOGO_SIZE = 56; string LOGO_FILE = "Logo.bmp"; // short description string EA_DESCRIPTION = "Hello trader! I am InfoPanel EA"; //+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { // 1. Create panel infoPanel = new CPanel(); if(infoPanel == NULL) { Print("InfoPanel: allocation failed"); return(INIT_FAILED); } string panel_name = "InfoPanel_" + IntegerToString((int)ChartID()); if(!infoPanel.Create(ChartID(), panel_name, 0, PANEL_X1, PANEL_Y1, PANEL_X2, PANEL_Y2)) { Print("InfoPanel: Create() failed"); delete infoPanel; infoPanel = NULL; return(INIT_FAILED); } infoPanel.BorderType(BORDER_RAISED); infoPanel.Text("InfoPanel"); infoPanel.ColorBackground(clrSilver); infoPanel.ColorBorder(clrBlack); // 2. Create label inside panel int label_left = PANEL_X1 + PADDING; int label_top = PANEL_Y1 + PADDING + 18; string txt_name = panel_name + "_txtDesc"; if(!txtDesc.Create(ChartID(), txt_name, 0, label_left, label_top)) { Print("InfoPanel: txtDesc.Create() failed"); delete infoPanel; infoPanel = NULL; return(INIT_FAILED); } txtDesc.Description(EA_DESCRIPTION); txtDesc.FontSize(11); // 3. Set panel as background if(!ObjectSetInteger(ChartID(), panel_name, OBJPROP_BACK, true)) PrintFormat("InfoPanel: unable to set OBJPROP_BACK for '%s' (non-fatal).", panel_name); // 4. Compute logo coordinates int logo_right = PANEL_X2 - PADDING; int logo_left = logo_right - LOGO_SIZE; int logo_top = PANEL_Y1 + PADDING; int logo_bottom = logo_top + LOGO_SIZE; string logo_name = panel_name + "_logo"; // 5. Create logo logo = new CPicture(); if(logo == NULL) { Print("InfoPanel: logo allocation failed (continuing without logo)"); } else { if(!logo.Create(ChartID(), logo_name, 0, logo_left, logo_top, logo_right, logo_bottom)) { Print("InfoPanel: logo.Create() failed (continuing without logo)"); delete logo; logo = NULL; } else { // Ensure logo is in foreground if(!ObjectSetInteger(ChartID(), logo_name, OBJPROP_BACK, false)) Print("InfoPanel: Failed to set OBJPROP_BACK=false for logo (non-fatal)."); // Log coordinates PrintFormat("InfoPanel: Logo coords: left=%d, top=%d, right=%d, bottom=%d", logo_left, logo_top, logo_right, logo_bottom); // Load built-in logo.bmp with correct relative path (no fullpath needed).You can use custom logo names as long you update the code. string bmp_path = "\\Images\\Logo.bmp"; bool ok = logo.BmpName(bmp_path); PrintFormat("InfoPanel: logo.BmpName('%s') returned %s", bmp_path, ok ? "true" : "false"); if(!ok) { Print("InfoPanel: LOGO load failed. Verify MT5 installation has MQL5\\Images\\dollar.bmp."); } else { Print("InfoPanel: Logo loaded successfully!"); } logo.Border(LOGO_SIZE); ChartRedraw(ChartID()); // Force redraw } } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(logo != NULL) { delete logo; logo = NULL; } txtDesc.Delete(); if(infoPanel != NULL) { delete infoPanel; infoPanel = NULL; } EventKillTimer(); } //+------------------------------------------------------------------+ //| OnTick | //+------------------------------------------------------------------+ void OnTick() { } //+------------------------------------------------------------------+ //| Chart events | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { }
Разместив логотип CPicture внутри прямоугольника CPanel и выведя его на передний план, мы провели небольшую, но эффективную демонстрацию: объединили три отдельных класса стандартной библиотеки — CPanel, CChartObjectLabel и CPicture — в один целостный элемент пользовательского интерфейса. Сначала мы создали панель, затем добавили описательную метку и, наконец, вычислили точные пиксельные координаты, чтобы вставить логотип так, чтобы он аккуратно располагался в правой части панели. Вызов ObjectSetInteger(..., OBJPROP_BACK, true) для панели и OBJPROP_BACK = false для логотипа позволили нам контролировать порядок отрисовки, так что изображение отображается над фоном, но позади других потенциальных интерактивных объектов. Мы также использовали ChartRedraw(), чтобы гарантировать, что терминал перерисовывается сразу после загрузки растрового изображения. Все эти шаги следовали одному и тому же ритму интеграции: включение, объявление, создание, настройка, проверка возвращаемых значений и очистка.
Из этого упражнения вышло несколько конкретных уроков об интеграции классов. Во-первых, важны расчёт координат и дисциплина именования — вычисляйте позиции относительно прямоугольника панели и используйте уникальные, предсказуемые имена объектов (мы использовали panel_name + "_logo"), чтобы избежать конфликтов и упростить отладку. Во-вторых, внешние ресурсы требуют осторожной обработки: мы сохранили растровое изображение в MQL5\Images, передали его относительный путь ("\Images\Logo.bmp") в функцию BmpName() и логировали результат, чтобы советник оставался устойчивым, если файл отсутствует. В-третьих, соблюдайте контракт жизненного цикла каждого класса: мы выделяли объекты с помощью new, проверяли каждый вызов Create() и BmpName() и всегда удаляли объекты в OnDeinit(), чтобы избежать появления лишних объектов графика или утечки памяти. В-четвертых, управление слоями и перерисовкой — это практические навыки: OBJPROP_BACK и ChartRedraw() позволяют нам управлять видимостью и обновлением предсказуемым образом.
Наконец, эта работа подтверждает повторяемый паттерн, применимый к любому классу стандартной библиотеки: определить общедоступные ключевые точки взаимодействия (создать, настроить методы-установщики, такие как Border()/BmpName() или Text()), интегрировать их в жизненный цикл советника (OnInit → runtime → OnDeinit), проверять каждый шаг с помощью четкого логирования и вычислять геометрию относительно существующих элементов, чтобы компоновка оставалась поддерживаемой. То, что выглядит как крошечная функция — логотип внутри панели — на самом деле является компактной площадкой для отработки более широкого набора навыков, которые мы развиваем: компоновка небольших, хорошо работающих модулей в более крупные, профессиональные интерфейсы. Теперь мы можем уверенно расширять панель (адаптивная компоновка, дополнительные статические строки метаданных или необязательные неинтерактивные значки), зная, что любой класс из стандартной библиотеки может быть интегрирован с использованием того же самого дисциплинированного подхода.
Тестирование
После компиляции кода в MetaEditor 5 мы разместили его на графике, чтобы наблюдать за нашей небольшой информационной панелью с описанием и логотипом — как показано на изображении ниже. Эта панель и ее компоненты могут рассматриваться как визуальный образ советника. Ту же идею можно применить для придания вашим будущим продуктам уникального и узнаваемого присутствия на графике. На данный момент мы можем гордиться тем, что успешно продемонстрировали концепцию и углубили наше понимание процесса интеграции.

Рис. 11. Тестирование InfoPanel_EA на графике
Заключение
В ходе этого упражнения мы успешно разработали повторяемую процедуру интеграции классов стандартной библиотеки в советник. Начиная с создания панели CPanel и заканчивая добавлением описательной метки и логотипа с помощью CChartObjectLabel и CPicture, мы изучили, как можно комбинировать и контролировать различные компоненты пользовательского интерфейса для получения качественного и функционального результата. Эта небольшая, но полная демонстрация отражает ту же архитектурную дисциплину, которая требуется для более крупных и сложных систем.
Цель здесь состояла не только в создании статической информационной панели, но и в усвоении рабочего паттерна — включение, объявление, инициализация, настройка и очистка — который универсально применяется во всей экосистеме классов MQL5. По мере дальнейшего продвижения в серии материалов по изучению стандартной библиотеки MQL5 это базовое упражнение упростит нашу будущую работу с более сложными элементами управления и составными модулями.
Хотя возможности для уточнений и творческих вариаций еще остаются, основная идея была достигнута: понимание того, как взаимодействуют модульные классы и как небольшие многократно используемые части могут превратиться в полноценные приложения. Повторение всячески поощряется — воссоздайте этот пример, поэкспериментируйте с пользовательскими цветами, альтернативными компоновками или новыми классами, такими как CButton, CDialog или CCheckBox. Каждая итерация углубляет ваше понимание того, как работает объектная модель MQL5.
Помните, модульность — ключ к прогрессу. Многие мощные строительные блоки уже существуют в стандартной библиотеке; наша задача как разработчиков — собрать их в интеллектуальные системы. Если вы стремитесь овладеть этим ремеслом, посвятите время изучению объектно-ориентированного программирования (ООП) — это даст вам понимание того, как проектировать, расширять и поддерживать собственные масштабируемые фреймворки.
Это упражнение может показаться простым на первый взгляд, но это важный шаг на вашем пути к профессиональной разработке на MQL5. Мы затронули многое, но впереди еще много интересного. Вложенный файл содержит полную реализацию — не стесняйтесь тестировать, дорабатывать и делиться своими мыслями. Давайте сделаем это обсуждение еще более содержательным с помощью ваших комментариев и креативных дополнений под этой статьей.
| Название файла | Описание |
|---|---|
| InfoPanel_EA.mq5 | Демонстрирует, как интегрировать несколько классов стандартной библиотеки в рамках советника. Советник создает информационную панель на графике с помощью CPanel, добавляет текстовую метку с описанием с помощью CChartObjectLabel и встраивает изображение логотипа с помощью класса CPicture. Этот пример служит основой для изучения модульного построения пользовательского интерфейса и взаимодействия классов в MQL5. |
| Logo.zip | Содержит логотип в поддерживаемом формате BMP, который можно извлечь в каталог Images терминала. Однако рекомендуем создать собственную пользовательскую версию, используя инструменты с открытым исходным кодом, такие как GIMP, для более персонализированного процесса разработки. |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/19834
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Автоматизация торговых стратегий на MQL5 (Часть 38): Торговля по скрытым дивергенциям RSI с фильтрами угла наклона
Возможности Мастера MQL5, которые вам нужно знать (Часть 83): Использование паттернов стохастического осциллятора и FrAMA — поведенческие архетипы
От начального до среднего уровня: Очереди, списки и деревья (III)
Автоматизация торговых стратегий на MQL5 (Часть 37): Поиск дивергенций и конвергенций RSI с визуализацией на графике
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования