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

 

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

В статье рассмотрим создание класса информационной панели для использования её в индикаторах и советниках. Это вводная статья в небольшой серии статей с шаблонами подключения и использования стандартных индикаторов в советниках. Начнем мы с создания панели — аналога окна данных 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. Сочувствую, потеря данных всегда очень неприятна, поэтому надежный бэкап наше все.

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

[Удален]  

Спасибо за очень хороший туториал.


Я изменил его на темный вид и в дальнейшем ищу возможность изменить цвета заголовка и данных.

Также я создал функцию для добавления текста простым способом

void DrawData(int cell, string title, string data, color clr ) {
   dashboard.DrawText(title,  dashboard.CellX(cell,0)+2,   dashboard.CellY(cell,0)+2, clr);
   dashboard.DrawText(data,   dashboard.CellX(cell,1)+2,   dashboard.CellY(cell,1)+2,90, clr);
   ChartRedraw(ChartID());
}

но мне трудно изменить цвета этих текстов, не знаю, что не так, так как цвета выглядят тусклыми, вот модифицированная функция DrawText

//+------------------------------------------------------------------+
//| Отобразите текстовое сообщение в указанных координатах |
//+------------------------------------------------------------------+
void CDashboard::DrawText(const string text,const int x,const int y,const color clr,const int width=WRONG_VALUE,const int height=WRONG_VALUE)
  {
//--- Объявите переменные, чтобы записать в них ширину и высоту текста
   int w=width;
   int h=height;
//--- Если ширина и высота текста, переданного в метод, имеют нулевые значения,
//--- затем все рабочее пространство полностью очищается с помощью прозрачного цвета
   if(width==0 && height==0)
      this.m_workspace.Erase(0x00FFFFFF);
//--- Иначе
   else
     {
      //--- Если переданные ширина и высота имеют значения по умолчанию (-1), мы получим их ширину и высоту из текста
      if(width==WRONG_VALUE && height==WRONG_VALUE)
         this.m_workspace.TextSize(text,w,h);
      //--- иначе,
      else
        {
         //--- если ширина, переданная в метод, имеет значение по умолчанию (-1) - получите ширину из текста, или
         //--- если ширина, переданная в метод, имеет значение больше нуля, используйте ширину, переданную в метод, или
         //--- если ширина, переданная в метод, имеет нулевое значение, используйте для нее значение 1
         w=(width ==WRONG_VALUE ? this.m_workspace.TextWidth(text)  : width>0  ? width  : 1);
         //--- если высота, переданная в метод, имеет значение по умолчанию (-1), получите высоту из текста, или
         //--- если высота, переданная в метод, имеет значение больше нуля, используйте высоту, переданную в метод, или
         //--- если высота, переданная в метод, имеет нулевое значение, используйте значение 1 для высоты
         h=(height==WRONG_VALUE ? this.m_workspace.TextHeight(text) : height>0 ? height : 1);
        }
      //--- Заполните пространство в соответствии с заданными координатами и полученными шириной и высотой прозрачным цветом (сотрите предыдущую запись)
      this.m_workspace.FillRectangle(x,y,x+w,y+h,0x00FFFFFF);
     }
//--- Выведите текст на место, освобожденное от предыдущего текста, и обновите рабочее пространство без перерисовки экрана
   this.m_workspace.TextOut(x,y,text,::ColorToARGB(clr,this.m_alpha));
   this.m_workspace.Update(false);
  }
 

Arpit T # :

//--- Выведите текст на место, освобожденное от предыдущего текста, и обновите рабочее пространство без перерисовки экрана
   this .m_workspace. TextOut (x,y,text,:: ColorToARGB (clr, this .m_alpha ));
   this .m_workspace.Update( false );

Выделенный цвет - это прозрачность самой панели, а не текста. Для текста лучше установить прозрачность (точнее, непрозрачность) на значение по умолчанию 255 - полностью непрозрачный текст. Но вы можете "поиграть" со значением непрозрачности, введя вместо this.m_alpha обычные числовые значения от 0 до 255. 0 - полностью прозрачный цвет, 255 - полностью непрозрачный.

[Удален]  
Artyom Trishkin #:

Выделенный цвет - это прозрачность самой панели, а не текста. Для текста лучше установить прозрачность (точнее, непрозрачность) на значение по умолчанию 255 - полностью непрозрачный текст. Но вы можете "поиграть" со значением непрозрачности, введя вместо this.m_alpha обычные числовые значения от 0 до 255. 0 - полностью прозрачный цвет, 255 - полностью непрозрачный.

Спасибо, теперь все хорошо


void DrawData(int cell, string title, string data, color clr ) {
   dashboard.DrawText(title,  dashboard.CellX(cell,0)+2,   dashboard.CellY(cell,0)+2, clr);
   dashboard.DrawText(data,   dashboard.CellX(cell,1)+2,   dashboard.CellY(cell,1)+2,90, clr);
   ChartRedraw(ChartID());
}

Выделенный код создавал проблему, я удалил его и все хорошо.

 
хорошо сделанная работа темный хорошо
[Удален]  
VIKRAM SINGH #:
хорошо сделанная работа темный хорошо

Спасибо

Вот модифицированный конструктор для темного вида в dashboard.mqh, если вы хотите использовать эту тему

//+------------------------------------------------------------------+
//| Конструктор|
//+------------------------------------------------------------------+
CDashboard::CDashboard(const uint id,const int x,const int y, const int w,const int h,const int wnd=-1) : 
                        m_id(id),
                        m_chart_id(::ChartID()),
                        m_program_type((ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE)),
                        m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)),
                        m_wnd(wnd==-1 ? GetSubWindow() : wnd),
                        m_chart_w((int)::ChartGetInteger(m_chart_id,CHART_WIDTH_IN_PIXELS,m_wnd)),
                        m_chart_h((int)::ChartGetInteger(m_chart_id,CHART_HEIGHT_IN_PIXELS,m_wnd)),
                        m_mouse_state(MOUSE_STATE_NOT_PRESSED),
                        m_x(x),
                        m_y(::ChartGetInteger(m_chart_id,CHART_SHOW_ONE_CLICK) ? (y<79 ? 79 : y) : y),
                        m_w(w),
                        m_h(h),
                        m_x_dock(m_x),
                        m_y_dock(m_y),
                        m_header(true),
                        m_butt_close(true),
                        m_butt_minimize(true),
                        m_butt_pin(true),
                        m_header_h(18),
                        
                        //--- Реализация заголовка панели
                        m_header_alpha(255),
                        m_header_alpha_c(m_header_alpha),
                        m_header_back_color(clrBlack),
                        m_header_back_color_c(m_header_back_color),
                        m_header_fore_color(clrSnow),
                        m_header_fore_color_c(m_header_fore_color),
                        m_header_border_color(clrSnow),
                        m_header_border_color_c(m_header_border_color),
                        m_title("Dashboard"),
                        m_title_font("Calibri"),
                        m_title_font_size(-100),
                        
                        //--- кнопка закрытия
                        m_butt_close_back_color(clrBlack),
                        m_butt_close_back_color_c(m_butt_close_back_color),
                        m_butt_close_fore_color(clrSnow),
                        m_butt_close_fore_color_c(m_butt_close_fore_color),
                        
                        //--- кнопка свернуть/развернуть
                        m_butt_min_back_color(clrBlack),
                        m_butt_min_back_color_c(m_butt_min_back_color),
                        m_butt_min_fore_color(clrSnow),
                        m_butt_min_fore_color_c(m_butt_min_fore_color),
                        
                        //--- кнопка с булавкой
                        m_butt_pin_back_color(clrBlack),
                        m_butt_pin_back_color_c(m_butt_min_back_color),
                        m_butt_pin_fore_color(clrSnow),
                        m_butt_pin_fore_color_c(m_butt_min_fore_color),
                        
                        //--- Реализация панели
                        m_alpha(255),
                        m_alpha_c(m_alpha),
                        m_fore_alpha(255),
                        m_fore_alpha_c(m_fore_alpha),
                        m_back_color(clrBlack),
                        m_back_color_c(m_back_color),
                        m_fore_color(clrSnow),
                        m_fore_color_c(m_fore_color),
                        m_border_color(clrSnow),
                        m_border_color_c(m_border_color),
                        m_font("Calibri"),
                        m_font_size(-100),
                        
                        m_minimized(false),
                        m_movable(true)
  {
//--- Установите разрешение для графика на отправку сообщений о событиях перемещения и нажатия кнопок мыши,
//--- события прокрутки мыши, а также создание/удаление графических объектов
   ::ChartSetInteger(this.m_chart_id,CHART_EVENT_MOUSE_MOVE,true);
   ::ChartSetInteger(this.m_chart_id,CHART_EVENT_MOUSE_WHEEL,true);
   ::ChartSetInteger(this.m_chart_id,CHART_EVENT_OBJECT_CREATE,true);
   ::ChartSetInteger(this.m_chart_id,CHART_EVENT_OBJECT_DELETE,true);
   
//--- Задайте имена глобальных переменных терминала для хранения координат панели, состояния свернутого/развернутого экрана и привязки
   this.m_name_gv_x=this.m_program_name+"_id_"+(string)this.m_id+"_"+(string)this.m_chart_id+"_X";
   this.m_name_gv_y=this.m_program_name+"_id_"+(string)this.m_id+"_"+(string)this.m_chart_id+"_Y";
   this.m_name_gv_m=this.m_program_name+"_id_"+(string)this.m_id+"_"+(string)this.m_chart_id+"_Minimize";
   this.m_name_gv_u=this.m_program_name+"_id_"+(string)this.m_id+"_"+(string)this.m_chart_id+"_Unpin";
   
//--- Если глобальная переменная не существует, создайте ее и запишите текущее значение,
//--- иначе - считываем в нее значение из глобальной переменной терминала
//--- Координата X
   if(!::GlobalVariableCheck(this.m_name_gv_x))
      ::GlobalVariableSet(this.m_name_gv_x,this.m_x);
   else
      this.m_x=(int)::GlobalVariableGet(this.m_name_gv_x);
//--- Координата Y
   if(!::GlobalVariableCheck(this.m_name_gv_y))
      ::GlobalVariableSet(this.m_name_gv_y,this.m_y);
   else
      this.m_y=(int)::GlobalVariableGet(this.m_name_gv_y);
//--- Свернуто/развернуто
   if(!::GlobalVariableCheck(this.m_name_gv_m))
      ::GlobalVariableSet(this.m_name_gv_m,this.m_minimized);
   else
      this.m_minimized=(int)::GlobalVariableGet(this.m_name_gv_m);
//--- Свернуто/не свернуто
   if(!::GlobalVariableCheck(this.m_name_gv_u))
      ::GlobalVariableSet(this.m_name_gv_u,this.m_movable);
   else
      this.m_movable=(int)::GlobalVariableGet(this.m_name_gv_u);

//--- Установите флаги, чтобы размер панели превышал размер окна графика
   this.m_higher_wnd=this.HigherWnd();
   this.m_wider_wnd=this.WiderWnd();

//--- Если графический ресурс панели создан,
   if(this.m_canvas.CreateBitmapLabel(this.m_chart_id,this.m_wnd,"P"+(string)this.m_id,this.m_x,this.m_y,this.m_w,this.m_h,COLOR_FORMAT_ARGB_NORMALIZE))
     {
      //--- установите шрифт холста и залейте холст прозрачным цветом
      this.m_canvas.FontSet(this.m_title_font,this.m_title_font_size,FW_BOLD);
      this.m_canvas.Erase(0x00FFFFFF);
     }
//--- иначе - сообщить журналу о неудачном создании объекта
   else
      ::PrintFormat("%s: Error. CreateBitmapLabel for canvas failed",(string)__FUNCTION__);

//--- Если создано рабочее пространство графического ресурса,
   if(this.m_workspace.CreateBitmapLabel(this.m_chart_id,this.m_wnd,"W"+(string)this.m_id,this.m_x+1,this.m_y+this.m_header_h,this.m_w-2,this.m_h-this.m_header_h-1,COLOR_FORMAT_ARGB_NORMALIZE))
     {
      //--- установите шрифт для рабочей области и залейте его прозрачным цветом
      this.m_workspace.FontSet(this.m_font,this.m_font_size);
      this.m_workspace.Erase(0x00FFFFFF);
     }
//--- иначе - сообщить журналу о неудачном создании объекта
   else
      ::PrintFormat("%s: Error. CreateBitmapLabel for workspace failed",(string)__FUNCTION__);
  }
 
Артем привет! Я разбираясь в создании инфо панели, не могу понять одну вещь! т.к я не совсем профи в этом.... В общем смотри, 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), то никаких указателей на него использовать не нужно было - обращение шло бы к нему точно так же посредством оператора "точка". И удалять бы его не нужно было по окончании работы - подсистема терминала сама это делает. Но это был бы единственный экземпляр класса в программе.

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