Обсуждение статьи "Делаем информационную панель для отображения данных в индикаторах и советниках"

 

Опубликована статья Делаем информационную панель для отображения данных в индикаторах и советниках:

В статье рассмотрим создание класса информационной панели для использования её в индикаторах и советниках. Это вводная статья в небольшой серии статей с шаблонами подключения и использования стандартных индикаторов в советниках. Начнем мы с создания панели — аналога окна данных MetaTrader 5.

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

Панель сделаем в виде прототипа окна данных в терминале и заполним её такими же данными:

Рис.1 Окно данных и информационная панель

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

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

Автор: Artyom Trishkin

 

Спасибо, что делитесь.

У данной реализации класса есть большой недостаток, если есть потребность в его наследовании.

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

2. Секция с параметрами объявлена как private, что не дает в потомке реализовать например другую цветовую схему или изменить размер заголовка

3. Понимаю, что работа не закончена, но некоторые функции не реализованы, например SetButtonClose(On/Off), SetButtonMinimize(On/Off)

С наличием исходника допиливание проблем не составляет, но все же...

 
Evgeny #:

Спасибо, что делитесь.

У данной реализации класса есть большой недостаток, если есть потребность в его наследовании.

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

2. Секция с параметрами объявлена как private, что не дает в потомке реализовать например другую цветовую схему или изменить размер заголовка

3. Понимаю, что работа не закончена, но некоторые функции не реализованы, например SetButtonClose(On/Off), SetButtonMinimize(On/Off)

С наличием исходника допиливание проблем не составляет, но все же...

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

1. В потомке есть свой конструктор, в его списке инициализации должен быть конструктор родителя. В нём и указываете требуемые размеры.

2. О цветовых схемах не вспоминал даже) Равно, как и о размере заголовка.

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

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

 
Artyom Trishkin #:

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

1. В потомке есть свой конструктор, в его списке инициализации должен быть конструктор родителя. В нём и указываете требуемые размеры.

2. О цветовых схемах не вспоминал даже) Равно, как и о размере заголовка.

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

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

1. Да, но просто когда вызов конструктора предка идет в  списке инициализации, то расчет параметров, которые будут в него переданы надо куда-то выносить. Особенно, если он не самый примитивный. 

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

2. Мелкие украшательства делают продукт качественней. Но у Вас в целом код красивый и решения интересные, приятно его читать.

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

Спасибо, ждем новых статей.

 
Артем привет! Я разбираясь в создании инфо панели, не могу понять одну вещь! т.к я не совсем профи в этом.... В общем смотри, CDashboard   * dashboard = NULL; Мы сделали экземпляр класса ... так? и тут же присваиваем ему NULL... так?  потом   в oninit,  описатель  присваиваем...  dashboard = new CDashboard(InpUniqID, InpPanelX, InpPanelY, 500, 700); Почему так? а не так CDashboard   dashboard;  Или с объектами это так работает? В голове какой-то бардак! Если тебе не затруднит?... по простому объяснить ... спасибо!
 
Igor Bakhrushen #:
Артем привет! Я разбираясь в создании инфо панели, не могу понять одну вещь! т.к я не совсем профи в этом.... В общем смотри, CDashboard   * dashboard = NULL; Мы сделали экземпляр класса ... так? и тут же присваиваем ему NULL... так?  потом   в oninit,  описатель  присваиваем...  dashboard = new CDashboard(InpUniqID, InpPanelX, InpPanelY, 500, 700); Почему так? а не так CDashboard   dashboard;  Или с объектами это так работает? В голове какой-то бардак! Если тебе не затруднит?... по простому объяснить ... спасибо!

Здравствуйте.

Таким образом

CDashboard  *dashboard = NULL;

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


А просто экземпляр класса объявляется так:

CDashboard   dashboard;

Но в данном случае объявить и создать экземпляр таким образом не получится - у класса нет конструктора без формальных параметров.

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

CDashboard   dashboard(InpUniqID, InpPanelX, InpPanelY, 200, 250);

------------------------

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

//--- Создаём объект панели
   dashboard=new CDashboard(InpUniqID,InpPanelX,InpPanelY,200,250);
   if(dashboard==NULL)
     {
      Print("Error. Failed to create dashboard object");
      return INIT_FAILED;
     }


Далее в OnDeinit() объект удаляется в памяти по этому указателю:

//--- Если объект панели существует - удаляем
   if(dashboard!=NULL)
      delete dashboard;

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

Получается, если вкратце, то в примере к статье

  1. объявляем переменную-указатель на будущий объект класса  и инициализируем её значением NULL,
  2. создаём новый объект класса и записываем указатель на него в созданную ранее переменную dashboard,
  3. при обращении к созданному объекту используем переменную-указатель и точку ( dashboard.AnyMethod() )
  4. по окончании работы удаляем динамически созданный объект класса по указателю на него.

Если бы был сразу же создан нужный экземпляр класса (CDashboard  dashboard), то никаких указателей на него использовать не нужно было - обращение шло бы к нему точно так же посредством оператора "точка". И удалять бы его не нужно было по окончании работы - подсистема терминала сама это делает. Но это был бы единственный экземпляр класса в программе.

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

 
Artyom Trishkin #:

Здравствуйте.

Таким образом

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


А просто экземпляр класса объявляется так:

Но в данном случае объявить и создать экземпляр таким образом не получится - у класса нет конструктора без формальных параметров.

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

------------------------

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


Далее в OnDeinit() объект удаляется в памяти по этому указателю:

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

Получается, если вкратце, то в примере к статье

  1. объявляем переменную-указатель на будущий объект класса  и инициализируем её значением NULL,
  2. создаём новый объект класса и записываем указатель на него в созданную ранее переменную dashboard,
  3. при обращении к созданному объекту используем переменную-указатель и точку ( dashboard.AnyMethod() )
  4. по окончании работы удаляем динамически созданный объект класса по указателю на него.

Если бы был сразу же создан нужный экземпляр класса (CDashboard  dashboard), то никаких указателей на него использовать не нужно было - обращение шло бы к нему точно так же посредством оператора "точка". И удалять бы его не нужно было по окончании работы - подсистема терминала сама это делает. Но это был бы единственный экземпляр класса в программе.

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

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

Собственно интерес к созданию инфо панели, это следствие DPI монитора, в моем случае 168. Comment(); сильно мелко получается!

Есть TerminalInfoInteger(TERMINAL_SCREEN_DPI)  хочу создавать с учетом разрешения... и хочу по цветовой группе,для удобства.

Может у вас такое решение есть? Ваш код хороший, понятно читается... я же, долго, непонятно, в результате приходится все несколько раз переписывать! Спасибо за вашу поддержку!  

 
Igor Bakhrushen #:

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

Собственно интерес к созданию инфо панели, это следствие DPI монитора, в моем случае 168. Comment(); сильно мелко получается!

Есть TerminalInfoInteger(TERMINAL_SCREEN_DPI)  хочу создавать с учетом разрешения... и хочу по цветовой группе,для удобства.

Может у вас такое решение есть? Ваш код хороший, понятно читается... я же, долго, непонятно, в результате приходится все несколько раз переписывать! Спасибо за вашу поддержку!  

Решение же есть прямо в примерах в справке:

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Галерея UI написанных на MQL

Artyom Trishkin, 2024.05.31 10:33

Здесь (TERMINAL_SCREEN_DPI):

Пример вычисления коэффициента масштабирования:

//--- создаём кнопку шириной 1.5 дюйма на экране
int screen_dpi = TerminalInfoInteger(TERMINAL_SCREEN_DPI); // получим DPI монитора пользователя
int base_width = 144;                                      // базовая ширина в экранных точках для стандартных мониторов c DPI=96
int width      = (button_width * screen_dpi) / 96;         // вычислим ширину кнопки для монитора пользователя (с учётом его DPI)
...
 
//--- вычисление коэффициента масштабирования в процентах
int scale_factor=(TerminalInfoInteger(TERMINAL_SCREEN_DPI) * 100) / 96;
//--- использование коэффициента масштабирования
width=(base_width * scale_factor) / 100;

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


 
Я, к сожалению, не имею 4К монитора, поэтому не могу проверить, и по той же причине пока не ввожу такой перерасчёт в библиотеку - делать наобум, без проверки - не серьёзно.
 
Artyom Trishkin #:
Я, к сожалению, не имею 4К монитора, поэтому не могу проверить, и по той же причине пока не ввожу такой перерасчёт в библиотеку - делать наобум, без проверки - не серьёзно.
Ясно... Спасибо! Потихоньку разберемся!
 
Artyom Trishkin #:
Я, к сожалению, не имею 4К монитора, поэтому не могу проверить, и по той же причине пока не ввожу такой перерасчёт в библиотеку - делать наобум, без проверки - не серьёзно.

Артем привет! Вот строится с учетом dpi... Но по сути можно пойти другим путем. Скажем задать размер заголовка, посчитать размер панели высоту строк и высоту текста, исходя из размера заголовка. Указываешь два параметра... и пофиг какой там dpi.. или с этим могут быть проблемы? Я не совсем уверен, Какое ваше мнение по поводу такого решения?