Улучшаем работу с Панелями: добавляем прозрачность, меняем цвет фона и наследуемся от CAppDialog/CWndClient
Содержание
- Введение
- Прозрачная панель при перемещении
- Как это сделано
- Теперь можно приступать к реализации
- Обработчик "OnDialogDragStart": начало перемещения панели
- Обработчик "OnDialogDragProcess": продолжение перемещения панели
- Обработчик "OnDialogDragEnd": окончание перемещения панели
- Добавим на панель кнопку и сделаем кнопку прозрачной при перемещении панели
- Добавим на панель две кнопки: управляем цветом фона панели и цветом заголовка
- Наследуемся от CAppDialog
- Наследуемся от CWndClient
- MyWndClient.mq5
- Горизонтальный скролл и скрытые элементы управления
- Клики по горизонтальному скроллу
- Новые проекты. Чем они могут помочь в изучении панелей?
- Заключение
Введение
Панелям на базе класса CAppDialog не хватает методов прямого доступа к свойствам элементов управления, из которых состоит панель — таким, как "Цвет фона" и "Цвет рамки". Поэтому все панели создаются одинаково серыми.
Не имея способа менять цвет элементов управления, нельзя реализовать дизайнерские задумки. Конечно, можно решить эту проблему наследованием и добавлением своих методов. Но для этого нужно будет вносить довольно много правок в создаваемый код. Есть ли более простой и быстрый способ получить доступ к свойствам "Цвет фона" и "Цвет рамки" для элементов управления панели?
Прозрачная панель при перемещении
Покажу сначала, что можно сделать для панели на базе класса CAppDialog (это пример работы кода "Live panel.mq5").
На этой анимации видно, что если панель перемещать, от панели остаётся только наружная рамка. При перемещении наружная рамка дополнительно меняет свой цвет в произвольном порядке. Когда перемещение окончено, форма снова становится обычной — появляется заливка рабочих областей.
Вся работа будет проводиться вокруг класса CDialog. Он расположен в файле [data folder]\MQL5\Include\Controls\Dialog.mqh.
Напомню, из каких объектов состоит панель (эти объекты объявлены в классе CDialog в секции private) и как они визуально воплощаются в графические элементы://+------------------------------------------------------------------+ //| Class CDialog | //| Usage: base class to create dialog boxes | //| and indicator panels | //+------------------------------------------------------------------+ class CDialog : public CWndContainer { private: //--- dependent controls CPanel m_white_border; // the "white border" object CPanel m_background; // the background object CEdit m_caption; // the window title object CBmpButton m_button_close; // the "Close" button object CWndClient m_client_area; // the client area object protected:
Для придания прозрачности панели при перетаскивании нужно учитывать четыре момента.
1. Нас будут интересовать графические элементы "Border" и "Back" (созданные объектами m_white_border и m_background класса CPanel) и элемент "Client" (созданный объектом m_client_area класса CWndClient). Как они создаются и какой им задаётся цвет, видно в функциях CDialog::CreateWhiteBorder, CDialog::CreateBackground и CDialog::CreateClientArea, соответственно.
2. Панель при своём создании получает уникальное имя — цифровой префикс, который ставится перед именами всех графических объектов (на рисунке ниже префикс — это цифры 03082):
3. В классе CDialog есть три обработчика перетаскивания:
Обработка перетаскивания | |
---|---|
OnDialogDragStart | Виртуальный обработчик события "DialogDragStart" элемента управления |
OnDialogDragProcess | Виртуальный обработчик события "DialogDragProcess" элемента управления |
OnDialogDragEnd | Виртуальный обработчик события "DialogDragEnd" элемента управления |
4. Фокус из статьи "Как создать графическую панель любой сложности и как это работает" — обход объектов панели (здесь ExtDialog — объект класса панели):
int total=ExtDialog.ControlsTotal(); for(int i=0;i<total;i++) { CWnd*obj=ExtDialog.Control(i); ...
Почему при обходе цикла указатель на объект obj объявляется с типом CWnd?
Потому что CWnd — базовый класс, от которого происходят все остальные классы-потомки:
Теперь можно приступать к реализации
Для работы с цветом понадобятся два макроса, а для перехвата перемещения панели нужно переопределить три функции из класса CDialog:
//+------------------------------------------------------------------+ //| Live panel.mq5 | //| Copyright 2018, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property version "1.000" #property description "Панель меняет свою прозрачность при перемещении" #include <Controls\Dialog.mqh> #define XRGB(r,g,b) (0xFF000000|(uchar(r)<<16)|(uchar(g)<<8)|uchar(b)) #define GETRGB(clr) ((clr)&0xFFFFFF) //+------------------------------------------------------------------+ //| Class CLivePanel | //| Usage: main dialog of the Controls application | //+------------------------------------------------------------------+ class CLivePanel : public CAppDialog { public: CLivePanel(void); ~CLivePanel(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); //--- handlers of drag virtual bool OnDialogDragStart(void); virtual bool OnDialogDragProcess(void); virtual bool OnDialogDragEnd(void); };
Стандартную часть работы с панелью (создание, удаление и передача событий) я пропущу, а вот на обработчиках перемещения остановлюсь подробнее.
Обработчик "OnDialogDragStart": начало перемещения панели
Получаем префикс, а дальше в цикле обходим все объекты панели и ищем имя "Border", "Back" или "Client" с префиксом:
//+------------------------------------------------------------------+ //| Start dragging the dialog box | //+------------------------------------------------------------------+ bool CLivePanel::OnDialogDragStart(void) { string prefix=Name(); int total=ExtDialog.ControlsTotal(); for(int i=0;i<total;i++) { CWnd*obj=ExtDialog.Control(i); string name=obj.Name(); //--- if(name==prefix+"Border") { CPanel *panel=(CPanel*) obj; panel.ColorBackground(clrNONE); ChartRedraw(); } if(name==prefix+"Back") { CPanel *panel=(CPanel*) obj; panel.ColorBackground(clrNONE); ChartRedraw(); } if(name==prefix+"Client") { CWndClient *wndclient=(CWndClient*) obj; wndclient.ColorBackground(clrNONE); wndclient.ColorBorder(clrNONE); ChartRedraw(); } } return(CDialog::OnDialogDragStart()); }
Когда объекты найдены, удаляем цвет фона (метод "ColorBackground") и рамки (метод "ColorBorder"), применяя цвет clrNONE. Таким образом реализуется прозрачность формы.
Обработчик "OnDialogDragProcess": продолжение перемещения панели
Ищем только один объект — "Back", и динамически меняем его цвет (при помощи двух макросов XRGB и GETRGB):
//+------------------------------------------------------------------+ //| Continue dragging the dialog box | //+------------------------------------------------------------------+ bool CLivePanel::OnDialogDragProcess(void) { string prefix=Name(); int total=ExtDialog.ControlsTotal(); for(int i=0;i<total;i++) { CWnd*obj=ExtDialog.Control(i); string name=obj.Name(); if(name==prefix+"Back") { CPanel *panel=(CPanel*) obj; color clr=(color)GETRGB(XRGB(rand()%255,rand()%255,rand()%255)); panel.ColorBorder(clr); ChartRedraw(); } } return(CDialog::OnDialogDragProcess()); }
Обработчик "OnDialogDragEnd": окончание перемещения панели
Восстанавливаем цвет фона и рамки объектам "Border", "Back" или "Client":
//+------------------------------------------------------------------+ //| End dragging the dialog box | //+------------------------------------------------------------------+ bool CLivePanel::OnDialogDragEnd(void) { string prefix=Name(); int total=ExtDialog.ControlsTotal(); for(int i=0;i<total;i++) { CWnd*obj=ExtDialog.Control(i); string name=obj.Name(); //--- if(name==prefix+"Border") { CPanel *panel=(CPanel*) obj; panel.ColorBackground(CONTROLS_DIALOG_COLOR_BG); panel.ColorBorder(CONTROLS_DIALOG_COLOR_BORDER_LIGHT); ChartRedraw(); } if(name==prefix+"Back") { CPanel *panel=(CPanel*) obj; panel.ColorBackground(CONTROLS_DIALOG_COLOR_BG); color border=(m_panel_flag) ? CONTROLS_DIALOG_COLOR_BG : CONTROLS_DIALOG_COLOR_BORDER_DARK; panel.ColorBorder(border); ChartRedraw(); } if(name==prefix+"Client") { CWndClient *wndclient=(CWndClient*) obj; wndclient.ColorBackground(CONTROLS_DIALOG_COLOR_CLIENT_BG); wndclient.ColorBorder(CONTROLS_DIALOG_COLOR_CLIENT_BORDER); ChartRedraw(); } } return(CDialog::OnDialogDragEnd()); }
Добавим на панель кнопку и сделаем кнопку прозрачной при перемещении панели
Это пример содержится в коде "Live panel and Button.mq5"
Чтобы работать с кнопкой, первым делом необходимо подключить в наш советник класс кнопки и добавить макросы, отвечающие за ее позиционирование и размер:
#property description "Панель меняет свою прозрачность при перемещении," #property description " но при этом добавленная кнопка остаётся в своём цвете" #include <Controls\Dialog.mqh> #include <Controls\Button.mqh> #define XRGB(r,g,b) (0xFF000000|(uchar(r)<<16)|(uchar(g)<<8)|uchar(b)) #define GETRGB(clr) ((clr)&0xFFFFFF) //+------------------------------------------------------------------+ //| defines | //+------------------------------------------------------------------+ //--- indents and gaps #define INDENT_LEFT (11) // indent from left (with allowance for border width) #define INDENT_TOP (11) // indent from top (with allowance for border width) #define CONTROLS_GAP_X (5) // gap by X coordinate //--- for buttons #define BUTTON_WIDTH (100) // size by X coordinate #define BUTTON_HEIGHT (20) // size by Y coordinate //+------------------------------------------------------------------+ //| Class CLivePanelAndButton | //| Usage: main dialog of the Controls application | //+------------------------------------------------------------------+ class CLivePanelAndButton : public CAppDialog
Также, чтобы иметь возможность работы с кнопкой, нужно объявить объект класса CButton:
//+------------------------------------------------------------------+ //| Class CLivePanelAndButton | //| Usage: main dialog of the Controls application | //+------------------------------------------------------------------+ class CLivePanelAndButton : public CAppDialog { private: CButton m_button1; // the button object public: CLivePanelAndButton(void);
и процедуру создания кнопки:
virtual bool OnDialogDragEnd(void); protected: //--- create dependent controls bool CreateButton1(void); };
Код "CreateButton1" — после создания кнопки не забываем добавить кнопку к создаваемой панели:
//+------------------------------------------------------------------+ //| Create the "Button1" button | //+------------------------------------------------------------------+ bool CLivePanelAndButton::CreateButton1(void) { //--- coordinates int x1=INDENT_LEFT; // x1 = 11 pixels int y1=INDENT_TOP; // y1 = 11 pixels int x2=x1+BUTTON_WIDTH; // x2 = 11 + 100 = 111 pixels int y2=y1+BUTTON_HEIGHT; // y2 = 11 + 20 = 32 pixels //--- create if(!m_button1.Create(0,"Button1",0,x1,y1,x2,y2)) return(false); if(!m_button1.Text("Button1")) return(false); if(!Add(m_button1)) return(false); //--- succeed return(true); }
Вот результат, если на панель из примера выше добавить кнопку:
Как видно, при перемещении панель становится прозрачной, а вот добавленный элемент управления — кнопка — остаётся непрозрачной. Здесь есть варианты написания кода на любителя: кому-то будет достаточно только прозрачного фона панели при перемещении, а кому-то захочется, чтобы и кнопка тоже при этом становилась прозрачной. Поработаем над вторым вариантом: сделаем кнопку прозрачной при перемещении панели.
Делаем кнопку также прозрачной при перемещении панели
Сделаем это при помощи кода "Live panel and transparent Button.mq5".
Когда на панель добавляется простой или комбинированный элемент управления, то создающий его объект добавляется к объекту m_client_area (напомню, что объект m_client_area объявляется в классе CDialog). Следовательно, при обнаружении перемещения панели нужно организовать обход в цикле по всем объектам, добавленным к m_client_area. Делать это удобно в первом обработчике — "OnDialogDragStart" (начало перемещения панели):
//+------------------------------------------------------------------+ //| Start dragging the dialog box | //+------------------------------------------------------------------+ bool CLivePaneTransparentButton::OnDialogDragStart(void) { string prefix=Name(); int total=ExtDialog.ControlsTotal(); for(int i=0;i<total;i++) { CWnd*obj=ExtDialog.Control(i); string name=obj.Name(); //--- if(name==prefix+"Border") { CPanel *panel=(CPanel*) obj; panel.ColorBackground(clrNONE); ChartRedraw(); } if(name==prefix+"Back") { CPanel *panel=(CPanel*) obj; panel.ColorBackground(clrNONE); ChartRedraw(); } if(name==prefix+"Client") { CWndClient *wndclient=(CWndClient*) obj; wndclient.ColorBackground(clrNONE); wndclient.ColorBorder(clrNONE); //--- int client_total=wndclient.ControlsTotal(); for(int j=0;j<client_total;j++) { CWnd*client_obj=wndclient.Control(j); string client_name=client_obj.Name(); if(client_name=="Button1") { CButton *button=(CButton*) client_obj; button.ColorBackground(clrNONE); ChartRedraw(); } } ChartRedraw(); } } return(CDialog::OnDialogDragStart()); }
При первом обнаружении перемещения и панель, и кнопка станут прозрачными.
Второй наш обработчик — "OnDialogDragProcess" (продолжение перемещения панели) менять не будем. А вот третий — "OnDialogDragEnd" (окончание перемещения панели) изменим, так как нужно возвратить цвет для кнопки:
//+------------------------------------------------------------------+ //| End dragging the dialog box | //+------------------------------------------------------------------+ bool CLivePaneTransparentButton::OnDialogDragEnd(void) { string prefix=Name(); int total=ExtDialog.ControlsTotal(); for(int i=0;i<total;i++) { CWnd*obj=ExtDialog.Control(i); string name=obj.Name(); //--- if(name==prefix+"Border") { CPanel *panel=(CPanel*) obj; panel.ColorBackground(CONTROLS_DIALOG_COLOR_BG); panel.ColorBorder(CONTROLS_DIALOG_COLOR_BORDER_LIGHT); ChartRedraw(); } if(name==prefix+"Back") { CPanel *panel=(CPanel*) obj; panel.ColorBackground(CONTROLS_DIALOG_COLOR_BG); color border=(m_panel_flag) ? CONTROLS_DIALOG_COLOR_BG : CONTROLS_DIALOG_COLOR_BORDER_DARK; panel.ColorBorder(border); ChartRedraw(); } if(name==prefix+"Client") { CWndClient *wndclient=(CWndClient*) obj; wndclient.ColorBackground(CONTROLS_DIALOG_COLOR_CLIENT_BG); wndclient.ColorBorder(CONTROLS_DIALOG_COLOR_CLIENT_BORDER); //--- int client_total=wndclient.ControlsTotal(); for(int j=0;j<client_total;j++) { CWnd*client_obj=wndclient.Control(j); string client_name=client_obj.Name(); if(client_name=="Button1") { CButton *button=(CButton*) client_obj; button.ColorBackground(CONTROLS_BUTTON_COLOR_BG); ChartRedraw(); } } ChartRedraw(); } } return(CDialog::OnDialogDragEnd()); }
Теперь в коде "Live panel and transparent Button.mq5" полностью реализовано изменение цвета панели и кнопки при перемещении панели:
Добавим на панель две кнопки: управляем цветом фона панели и цветом заголовка
Это пример содержится в коде "Live panel and button Clicks.mq5", и создан он на основе предыдущего кода "Live panel and transparent Button.mq5". Только теперь вместо событий перемещения панели мы будем "ловить" события клика по кнопкам. Также на панели будут две кнопки: одна кнопка отвечает за смену цвета фона панели, вторая — за смену цвета ее заголовка .
Чтобы отлавливать события, связанные с элементами управления, добавленными на панель, необходимо объявить обработчик событий и прописать сам обработчик:
//+------------------------------------------------------------------+ //| Class CLivePaneButtonClicks | //| Usage: main dialog of the Controls application | //+------------------------------------------------------------------+ class CLivePaneButtonClicks : public CAppDialog { private: CButton m_button1; // the button object CButton m_button2; // the button object public: CLivePaneButtonClicks(void); ~CLivePaneButtonClicks(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); //--- chart event handler virtual bool OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); protected: //--- create dependent controls bool CreateButton1(void); bool CreateButton2(void); //--- handlers of the dependent controls events void OnClickButton1(void); void OnClickButton2(void); }; //+------------------------------------------------------------------+ //| Event Handling | //+------------------------------------------------------------------+ EVENT_MAP_BEGIN(CLivePaneButtonClicks) ON_EVENT(ON_CLICK,m_button1,OnClickButton1) ON_EVENT(ON_CLICK,m_button2,OnClickButton2) EVENT_MAP_END(CAppDialog) //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+
Обработчик есть не что иное, как метод OnEvent, записанный макросами из блока "Events" и из блока "Macro of event handling map" файла "Defines.mqh" (подробнее см. в статье Как создать графическую панель любой сложности и как это работает).
Обработчик читается так:
- событие OnEvent для класса CLivePaneButtonClicks:
- если есть клик по элементу управления "m_button1" — вызываем обработчик "OnClickButton1"
- если есть клик по элементу управления "m_button2" — вызываем обработчик "OnClickButton2"
- возврат события OnEvent для родительского класса CAppDialog
Обработчики "OnClickButton1" и "OnClickButton2"
В обоих обработчиках организован обход в цикле по всем объектам, из которых состоит панель (они перечислены в пункте Как это сделано) — в данном случае по всем объектам объект-панели "ExtDialog". В результате вызывается метод CWndContainer::ControlsTotal()
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CLivePaneButtonClicks::OnClickButton1(void) { string prefix=Name(); int total=ExtDialog.ControlsTotal(); for(int i=0;i<total;i++) { CWnd*obj=ExtDialog.Control(i); string name=obj.Name(); //--- if(name==prefix+"Client") { CWndClient *wndclient=(CWndClient*) obj; color clr=(color)GETRGB(XRGB(rand()%255,rand()%255,rand()%255)); wndclient.ColorBackground(clr); ChartRedraw(); return; } } } //+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CLivePaneButtonClicks::OnClickButton2(void) { string prefix=Name(); int total=ExtDialog.ControlsTotal(); for(int i=0;i<total;i++) { CWnd*obj=ExtDialog.Control(i); string name=obj.Name(); //--- if(name==prefix+"Caption") { CEdit *edit=(CEdit*) obj; color clr=(color)GETRGB(XRGB(rand()%255,rand()%255,rand()%255)); edit.ColorBackground(clr); ChartRedraw(); return; } } }
В обработчике "OnClickButton1" ищем объект-клиентскую область с именем prefix+"Client" (это будет объект класса CWndClient), а в обработчике "OnClickButton2" — объект-заголовок с именем prefix+"Caption" (это будет объект класса CEdit). В обоих случаях рандомно выбираем цвет фона для найденных обьектов. Вот как выглядит результат:
Наследуемся от CAppDialog
Схема реализации отличается от применяемой в примерах стандартной библотеки (\MQL5\Experts\Examples\Controls\ и \MQL5\Indicators\Examples\Panels\SimplePanel\), а именно: в подключаемом файле "MyAppDialog.mqh" создаётся класс "CMyAppDialog", унаследованный от CAppDialog. В классе реализованы только три метода управления цветом формы и заголовка. В нем нет методов создания добавленных элементов управления, обработчика OnEvent и обработчиков клика по кнопкам (добавленным элементам управления).
Объекты класса CButton (добавленные элементы управления, две кнопки) создаются в запускаемом файле "MyAppWindow.mq5". Также в файле "MyAppWindow.mq5", в обработчике OnChartEvent, отлавливаются события клика по кнопкам и вызываются методы изменения цвета.
В нашем классе добавим три метода:
- CMyAppDialog::ColorBackground — установка цвета фона,
- void CMyAppDialog::ColorCaption — установка цвета заголовка,
- color CMyAppDialog::ColorCaption — получение цвета заголовка.
Алгоритм доступа к свойствам объектов аналогичен предыдущим кодам: в цикле обход всех объектов, составляющих панель, и сопоставление имени объекта. Не хватает еще одного метода — получения цвета фона. Но это невозможно сделать простыми средствами.
//+------------------------------------------------------------------+ //| MyAppDialog.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property version "1.00" #property description "Класс CMyAppDialog, унаслелованный от CAppDialog" #property description "Добавлены методы для установки цвета фона и заголовка" #include <Controls\Dialog.mqh> //+------------------------------------------------------------------+ //| Class CLivePanelTwoButtons | //| Usage: main dialog of the Controls application | //+------------------------------------------------------------------+ class CMyAppDialog : public CAppDialog { public: void ColorBackground(const color clr); color ColorCaption(void); void ColorCaption(const color clr); //--- конструтор и деструктор public: CMyAppDialog(void){}; ~CMyAppDialog(void){}; }; //+------------------------------------------------------------------+ //| Устанавливает цвет фона | //+------------------------------------------------------------------+ void CMyAppDialog::ColorBackground(const color clr) { string prefix=Name(); int total=ControlsTotal(); for(int i=0;i<total;i++) { CWnd*obj=Control(i); string name=obj.Name(); //--- if(name==prefix+"Client") { CWndClient *wndclient=(CWndClient*) obj; wndclient.ColorBackground(clr); ChartRedraw(); return; } } //--- } //+------------------------------------------------------------------+ //| Устанавливает цвет заголовка | //+------------------------------------------------------------------+ void CMyAppDialog::ColorCaption(const color clr) { string prefix=Name(); int total=ControlsTotal(); for(int i=0;i<total;i++) { CWnd*obj=Control(i); string name=obj.Name(); //--- if(name==prefix+"Caption") { CEdit *edit=(CEdit*) obj; edit.ColorBackground(clr); ChartRedraw(); return; } } //--- } //+------------------------------------------------------------------+ //| Получает цвет заголовка | //+------------------------------------------------------------------+ color CMyAppDialog::ColorCaption(void) { string prefix=Name(); int total=ControlsTotal(); color clr=clrNONE; for(int i=0;i<total;i++) { CWnd*obj=Control(i); string name=obj.Name(); //--- if(name==prefix+"Caption") { CEdit *edit=(CEdit*) obj; clr=edit.ColorBackground(clr); return clr; } } //--- вернем цвет return clr; } //+------------------------------------------------------------------+
"MyAppWindow.mq5" — запускающий файл. В этом файле объявлены макросы XRGB и GETRGB для генерации цвета. В OnInit создаётся панель, добавляются кнопки и сама панель запускается в работу.
//+------------------------------------------------------------------+ //| MyAppWindow.mq5 | //| Copyright 2018, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property version "1.00" #property description "Приложение MyAppWindow на основе класса CMyAppDialog" #property description "Добавлены кнопки для установки цвета фона и заголовка" #include "MyAppDialog.mqh" #include <Controls\Button.mqh> //--- макросы для работы с цветом #define XRGB(r,g,b) (0xFF000000|(uchar(r)<<16)|(uchar(g)<<8)|uchar(b)) #define GETRGB(clr) ((clr)&0xFFFFFF) //+------------------------------------------------------------------+ //| defines | //+------------------------------------------------------------------+ //--- indents and gaps #define INDENT_LEFT (11) // indent from left (with allowance for border width) #define INDENT_TOP (11) // indent from top (with allowance for border width) #define CONTROLS_GAP_X (5) // gap by X coordinate //--- for buttons #define BUTTON_WIDTH (100) // size by X coordinate #define BUTTON_HEIGHT (20) // size by Y coordinate //--- CMyAppDialog AppWindow; CButton m_button1; // the button object CButton m_button2; // the button object //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create application dialog if(!AppWindow.Create(0,"CMyAppDialog: change Back and Caption colors",0,40,40,380,344)) return(INIT_FAILED); //--- create dependent controls if(!CreateBackButton()) return(false); if(!CreateCaptionButton()) return(false); //--- run application AppWindow.Run(); //--- succeed return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Comment(""); //--- destroy dialog AppWindow.Destroy(reason); } //+------------------------------------------------------------------+ //| Expert chart event function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, // event ID const long& lparam, // event parameter of the long type const double& dparam, // event parameter of the double type const string& sparam) // event parameter of the string type { //--- сначала мы обработаем события кнопок if((StringFind(sparam,"Back")!=-1) && id==(CHARTEVENT_OBJECT_CLICK)) { Print(__FUNCSIG__," sparam=",sparam); AppWindow.ColorBackground(GetRandomColor()); } if((StringFind(sparam,"Caption")!=-1) && id==(CHARTEVENT_OBJECT_CLICK)) { Print(__FUNCSIG__," sparam=",sparam); AppWindow.ColorCaption(GetRandomColor()); } //--- а теперь остальные события обработает метод класса CMyAppDialog AppWindow.ChartEvent(id,lparam,dparam,sparam); } //+------------------------------------------------------------------+ //| Create the "Button1" button | //+------------------------------------------------------------------+ bool CreateBackButton(void) { //--- coordinates int x1=INDENT_LEFT; // x1 = 11 pixels int y1=INDENT_TOP; // y1 = 11 pixels int x2=x1+BUTTON_WIDTH; // x2 = 11 + 100 = 111 pixels int y2=y1+BUTTON_HEIGHT; // y2 = 11 + 20 = 32 pixels //--- create if(!m_button1.Create(0,"Back",0,x1,y1,x2,y2)) return(false); if(!m_button1.Text("Back")) return(false); if(!AppWindow.Add(m_button1)) return(false); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Create the "Button2" | //+------------------------------------------------------------------+ bool CreateCaptionButton(void) { //--- coordinates int x1=INDENT_LEFT+2*(BUTTON_WIDTH+CONTROLS_GAP_X); // x1 = 11 + 2 * (100 + 5) = 221 pixels int y1=INDENT_TOP; // y1 = 11 pixels int x2=x1+BUTTON_WIDTH; // x2 = 221 + 100 = 321 pixels int y2=y1+BUTTON_HEIGHT; // y2 = 11 + 20 = 31 pixels //--- create if(!m_button2.Create(0,"Caption",0,x1,y1,x2,y2)) return(false); if(!m_button2.Text("Caption")) return(false); if(!AppWindow.Add(m_button2)) return(false); //--- succeed return(true); } //+------------------------------------------------------------------+ //| Получает цвет случайным образом | //+------------------------------------------------------------------+ color GetRandomColor() { color clr=(color)GETRGB(XRGB(rand()%255,rand()%255,rand()%255)); return clr; } //+------------------------------------------------------------------+
Также в основном файле находится обработчик событий OnChartEvent. Он безусловно пересылает все события в панель, но при обнаружении события клика по одной из кнопок (CHARTEVENT_OBJECT_CLICK) вызывает методы класса панели (AppWindow.ColorBackground или AppWindow.ColorCaption).
Так работает связка из двух файлов: главного запускающего mq5 и включаемого mqh, в котором находится класс панели.
Наследуемся от CWndClient
В этом примере разберём наследование от CWndClient: создадим объект класса "CWndClient". Этот объект будет содержать такой функционал:
- создание объекта "CMyWndClient" — клиентской области панели;
- создание объекта добавления двух кнопок в клиентскую область;
- обработчики кликов по добавленным кнопкам (изменение цвета фона клиенской области и цвета заголовка панели);
- кроме того, для клиенской области будет включён горизонтальный скролл (помните, что класс CWndClient представляет собой комбинированный элемент управления "Клиентская область" и является базовым классом для создания областей с полосами прокрутки);
- соответственно, будут и обработчики кликов по горизонтальному скроллу (перемещение добавленных кнопок по клиенской области).
Остановимся подробнее на файлах MyWndClient.mq5 и MyWndClient.mqh.
Отличие от примеров стандартной библотеки (\MQL5\Experts\Examples\Controls\ и \MQL5\Indicators\Examples\Panels\SimplePanel\) состоит в том, что в подключаемом файле находится класс, наследованный от класса CWndClient 10 — клиентской области. Полный цикл создания панели выглядит так.
- Создаётся панель (объект AppWindow класса CAppDialog вызывает метод Create).
- Создаётся наша клиентская область (объект ClientArea класса CMyWndClient, из подключаемого файла MyWndClient.mqh вызывает метод Create).
- Созданная клиентская область добавляется в панель (по сути, ложится поверх клиенской области панели).
CAppDialog AppWindow; CMyWndClient ClientArea; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create application dialog bool result_create=false; if(!InpTwoButtonsVisible) { //--- after creation of the panel, one button will be visible result_create=AppWindow.Create(0,"CAppDialog with CMyWndClient",0,40,40,360,344); } else { //--- after creation of the panel, will two buttons are visible result_create=AppWindow.Create(0,"CAppDialog with CMyWndClient",0,40,40,420,344); } if(!result_create) return(INIT_FAILED); //--- create the panel PrintFormat("Application Rect: Height=%d Width=%d",AppWindow.Rect().Height(),AppWindow.Rect().Width()); CRect inner_rect=ClientArea.GetClientRect(GetPointer(AppWindow)); PrintFormat("Client Area: Height=%d Width=%d",inner_rect.Height(),inner_rect.Width()); ClientArea.Create(0,"MyWndClient",0,0,0,inner_rect.Width(),inner_rect.Height()); AppWindow.Add(ClientArea); //--- установим владельца ClientArea.SetOwner(GetPointer(AppWindow)); //--- скрывать невидимое ClientArea.HideInvisble(HideInvisble); //--- run application AppWindow.Run(); //--- succeed return(INIT_SUCCEEDED); }
В запускаемом файле есть два входных параметра:
- ширина панели — создание панели нормальной ширины или широкой панели;
- скрыть невидимое — отображать или скрывать скрытые элементы управления.
При создании панели учитывается только один входной параметр — "ширина панели". Вот так мы создаём панель нормальной ширины:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create application dialog bool result_create=false; if(!InpTwoButtonsVisible) { //--- after creation of the panel, one button will be visible result_create=AppWindow.Create(0,"CAppDialog with CMyWndClient",0,40,40,360,344); } else { //--- after creation of the panel, will two buttons are visible result_create=AppWindow.Create(0,"CAppDialog with CMyWndClient",0,40,40,420,344); } if(!result_create) return(INIT_FAILED); //--- create the panel
а так — широкую панель:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create application dialog bool result_create=false; if(!InpTwoButtonsVisible) { //--- after creation of the panel, one button will be visible result_create=AppWindow.Create(0,"CAppDialog with CMyWndClient",0,40,40,360,344); } else { //--- after creation of the panel, will two buttons are visible result_create=AppWindow.Create(0,"CAppDialog with CMyWndClient",0,40,40,420,344); } if(!result_create) return(INIT_FAILED); //--- create the panel
Код создания панели отличается только задаваемой шириной (360 и 420), и при создании двух кнопок эта ширина не учитывается. Сравните две последних картинки. А теперь наложите их одну на другую:
Видим, что кнопка "Caption" немного выходит за границу панели нормальной ширины — за границу клиенской области. Когда добавляемый элемент управления выходит за границы, он принудительно скрывается от пользователя (но не удаляется и не уничтожается). Процедура определения того, нужно ли скрыть элемент управления, запускается, когда он добавляется в клиентскую область — при вызове метода CWndContainer::Add. В нашем примере метод Add вызывается в AddButton2:
//+------------------------------------------------------------------+ //| Create the "Button2" | //+------------------------------------------------------------------+ bool CMyWndClient::AddButton2(void) { ... if(!Add(m_button2)) { Print("Add(m_button2) --> false"); return(false); } //--- succeed return(true); }
//+------------------------------------------------------------------+ //| Add control to the group (by reference) | //+------------------------------------------------------------------+ bool CWndContainer::Add(CWnd &control) { //--- add by pointer return(Add((CWnd*)GetPointer(control))); }
и последовательно вызывается самое главное — именно здесь определяется, скрывать или нет элемент управления
//+------------------------------------------------------------------+ //| Add control to the group (by pointer) | //+------------------------------------------------------------------+ bool CWndContainer::Add(CWnd *control) { //--- check of pointer if(control==NULL) return(false); //--- correct the coordinates of added control control.Shift(Left(),Top()); //--- "projecting" the group flag "visibility" to the added element if(IS_VISIBLE && control.IsVisible()) { //--- element will be "visible" only if the group is "visible" and the element is completely "within" this group control.Visible(Contains(control)); } else control.Hide(); //--- "projecting" the group flag "enabled" to the added element if(IS_ENABLED) control.Enable(); else control.Disable(); //--- adding return(m_controls.Add(control)); }
Задание видимости объекта создаётся ТОЛЬКО В МОМЕНТ ДОБАВЛЕНИЯ элемента управления в клиенскую область. Это и есть недостаток, который может проявиться после сворачивания и разворачивания панели.
Пример: оба входных параметра установить в "false", затем свернуть и развернуть панель. В итоге после создания панели кнопка "Caption" создаётся, но она визуально скрыта (так как кнопка выходит за границу клиентской области — она скрывается при добавлении в клиентскую область). Но после того, как панель свернули и развернули, проверка на видимость добавленных элементов уже не производится, и поэтому кнопка Caption будет видна:
В данном файле находится класс, унаследованный от класса CWndClient — клиентской области. В этом файле сосредоточен весь функционал:
- по созданию нашей клиенской области,
- по созданию и добавлению элементов управления,
- по обработке события разворачивания панели
- по обработке событий клика на горизонтальной полосе прокрутки
- по обработке событий клика по кнопкам — изменение цвета фона клиенской области и цвета заголовка.
Горизонтальный скролл и скрытые элементы управления
Так как класс панели наследуется от класса CWndClient, а класс CWndClient представляет собой комбинированный элемент управления "Клиентская область" и является базовым классом для создания областей с полосами прокрутки, то включим в нашей клиентской области горизонтальную полосу прокрутки:
//+------------------------------------------------------------------+ //| Создание панели | //+------------------------------------------------------------------+ bool CMyWndClient::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2) { //--- if(!CWndClient::Create(chart,name,subwin,x1,y1,x2,y2)) return(false); //--- enable horizontal scrollbar if(!HScrolled(true)) return(false); m_scroll_h.MaxPos(5); m_scroll_h.CurrPos(5); Print("CurrPos: ",m_scroll_h.CurrPos()); if(!AddButton1())
Чтобы скролл можно было двигать, задаём количество его градаций: значение максимальной позиции, и сразу позиционируем скролл в крайнее правое положение.
Горизонтальный скролл также используется для отлавливания события разворачивания панели — для этого "ловим" событие ON_SHOW для объекта m_scroll_h и вызываем обработчик OnShowScrollH:
//+------------------------------------------------------------------+ //| Event Handling | //+------------------------------------------------------------------+ EVENT_MAP_BEGIN(CMyWndClient) ON_EVENT(ON_CLICK,m_button1,OnClickButton1) ON_EVENT(ON_CLICK,m_button2,OnClickButton2) ON_EVENT(ON_SHOW,m_scroll_h,OnShowScrollH) EVENT_MAP_END(CWndClient)
EVENT_MAP_BEGIN есть не что иное, как метод OnEvent, записанный макросами из блока "Events" и из блока "Macro of event handling map" файла "Defines.mqh" (подробнее см. в статье Как создать графическую панель любой сложности и как это работает).
В обработчике OnShowScrollH проверяем значение внутреннего флага m_hide_invisble (этот флаг принимает значение входной переменной "скрыть невидимое" файла MyWndClient.mq5 через метод CMyWndClient::HideInvisble), и если не нужно скрывать элементы — просто выходим из процедуры:
//+------------------------------------------------------------------+ //| Появился скролл скрываем/отображаем кнопки | //+------------------------------------------------------------------+ void CMyWndClient::OnShowScrollH(void) { if(!m_hide_invisble) return; int total=CWndClient::ControlsTotal(); for(int i=0;i<total;i++) { CWnd*obj=Control(i); string name=obj.Name(); //--- if(StringFind(name,"Button")!=-1) { CButton *button=(CButton*)obj; button.Visible(Contains(GetPointer(button))); ChartRedraw(); } } }
Если нужно скрывать невидимые элементы — тогда в цикле по всем объектам клиентской области ищем объекты, содержащие в своём имени "Button", и принудительно проверяем/назначаем видимость.
Клики по горизонтальному скроллу
Для кликов по горизонтальному скроллу зададим возможность перемещения двух добавленных кнопок по нашей клиентской области. Для этого переопределим обработчики OnScrollLineRight и OnScrollLineLeft — обработчики клика по кнопкам горизонтального скролла. Если был клик на правой кнопке горизонтального скролла, значит перемещаем кнопки (метод ShiftButton) на шаг m_scroll_size. Если был клик на левой кнопке — то двигаем кнопки на шаг "-m_scroll_size", то есть задаём отрицательное смещение:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ bool CMyWndClient::OnScrollLineRight(void) { Print(__FUNCTION__); ShiftButton(GetPointer(m_button1),m_scroll_size); ShiftButton(GetPointer(m_button2),m_scroll_size); return(true); } //+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ bool CMyWndClient::OnScrollLineLeft(void) { Print(__FUNCTION__); ShiftButton(GetPointer(m_button1),-m_scroll_size); ShiftButton(GetPointer(m_button2),-m_scroll_size); return(true); }В методе CMyWndClient::ShiftButton получаем объект с координатами кнопки, задаём сдвиг координат, смещаем кнопку и принудительно проверяем/задаём видимость кнопки:
//+------------------------------------------------------------------+ //| Сдвиг кнопки влево или вправо (в зависимости от shift) | //+------------------------------------------------------------------+ bool CMyWndClient::ShiftButton(CButton *button,const int shift) { Print(__FUNCTION__); //--- смещаем кнопку CRect rect=button.Rect(); rect.Move(rect.left+shift,rect.top); button.Move(rect.left,rect.top); button.Visible(Contains(GetPointer(button))); return(true); }
Вот как это выглядит (не забудьте параметр "скрыть невидимое" поставить в значение true):
Новые проекты. Чем они могут помочь в изучении панелей?
Чтобы что-то написать, всегда приходится изучать код. В случае с созданием панелей изучение классов может быть очень трудоёмким. В основном это связано с тем, что нет визуального представления структуры классов. А еще сразу очень трудно понять, какие классы стандартной библиотеки были вовлечены в создание панелей.
К счастью, не так давно были представлены Новые Проекты в редакторе MetaEditor.
Проект — это отдельный файл с расширением "MQPROJ", в котором хранятся настройки программы, параметры компиляции и информация обо всех используемых файлах. Для удобной работы с проектом предусмотрена отдельная вкладка в Навигаторе. В ней по категориям отображаются все используемые файлы: включаемые, ресурсные, заголовочные и т.д.
Посмотрите на описание отдельно создаваемой вкладки: "В ней по категориям отображаются все используемые файлы: включаемые, ресурсные, заголовочные и т.д."! А ведь это как раз, что нам надо!
Попробуем создать проект из последнего файла "Live panel and button Clicks.mq5". Для этого кликнем правой кнопкой по файлу "Live panel and button Clicks.mq5" и в выпадающем меню выберем пункт "Новый проект из исходного файла":
В результате будет создан новый проект, а в окне "Навигатор" откроется вкладка "Проект", в которой можно увидеть все используемые файлы:
и "Wnd.mqh" (в нем класс CWnd), и "Dialog.mqh (в нем классы CDialog и CAppDialog), и Button.mhq (класс CButton). Из этой вкладки удобно переходить в нужный класс. Это намного удобнее перехода по вкладкам редактора MetaEditor. Например, у меня есть небольшой "зоопарк" из самых различных файлов. Перейти из него, например в Dialog.mqh через поиск во вкладках проблематично:
Заключение
В статье показан довольно необычный способ доступа к свойствам "Цвет фона", "Цвет рамки" и "Цвет заголовка" для элементов управления панели. Лично я раньше такого способа не встречал. Весь фокус кроется в знании, что все элементы панели наследуются от родительского класса CWnd, а значит объект — класс создаваемой панели — есть контейнер для всех элементов управления. Значит, можно в цикле пройтись по всем элементам управления и получить/установить нужные свойства.
Наименование файла | Комментарий |
---|---|
Live panel.mq5 | Панель без добавленных элементов управления. При перетаскивании становится прозрачной |
Live panel and Button.mq5 | Панель с добавленной кнопкой. При перетаскивании панель становится прозрачной, а кнопка остаётся в своем цвете |
Live panel and transparent Button.mq5 | Панель с добавленной кнопкой. При перетаскивании и панель, и кнопка становятся прозрачными |
Live panel and button Clicks.mq5 | Панель с двумя кнопками. Обработка кликов по кнопкам: генерация цвета фона для панели и заголовка |
MyAppWindow.mq5 и MyAppDialog.mqh | Пример создания панели наследованием от CAppDialog |
MyWndClient.mq5 и MyWndClient.mqh | Пример создания панели наследованием от CWndClient — от класса клиентской области |
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Владимир, спасибо за статьи, очень полезный материал. У меня может быть ламерский вопрос...
Есть объект типа CBmpButton - кнопка с изображением. Так вот, само изображение кнопки выступает тут в качестве ресурса. Как получить доступ к его свойствам? Или нельзя?
Пример из Документации.
Там я поиграл с методом CControlsDialog::CreateBmpButton1():
Объект самого изображения на панели не находится с помощью функции ObjectFind(), что вполне ест-но. А как его найти, если это вообще возможно?
Спасибо.
Владимир, спасибо за статьи, очень полезный материал. У меня может быть ламерский вопрос...
Есть объект типа CBmpButton - кнопка с изображением. Так вот, само изображение кнопки выступает тут в качестве ресурса. Как получить доступ к его свойствам? Или нельзя?
Пример из Документации.
Там я поиграл с методом CControlsDialog::CreateBmpButton1():
Объект самого изображения на панели не находится с помощью функции ObjectFind(), что вполне ест-но. А как его найти, если это вообще возможно?
Спасибо.
Изображение (рисунок) - это не объект. Объекты - это линии, прямоугольники ... графические элементы.
Здравствуйте, Владимир. Вы добавили в окно дополнительно ClientArea. И затем используете метод CMyWndClient::ShiftButton для скроллинга кнопок. Это не очень удобно, поскольку, кнопок в окне может быть много. Также, помимо кнопок может быть еще очень много элементов UI. То есть, понимаете, писать методы для каждого элемента и не забыть вызвать их в обработчиках скроллинга, та ещё задачка. А не проще скролить ClientArea? Тогда, все элементы UI, содержащиеся в нем, будут скролиться автоматически. Достаточно прописать только скроллинг ClientArea в обработчиках.
Здравствуйте, Владимир. Вы добавили в окно дополнительно ClientArea. И затем используете метод CMyWndClient::ShiftButton для скроллинга кнопок. Это не очень удобно, поскольку, кнопок в окне может быть много. Также, помимо кнопок может быть еще очень много элементов UI. То есть, понимаете, писать методы для каждого элемента и не забыть вызвать их в обработчиках скроллинга, та ещё задачка. А не проще скролить ClientArea? Тогда, все элементы UI, содержащиеся в нем, будут скролиться автоматически. Достаточно прописать только скроллинг ClientArea в обработчиках.
Напишите пожалуйста такой пример. Думаю всем будет интересна такая реализация.
Напишите пожалуйста такой пример. Думаю всем будет интересна такая реализация.
Немного подумав я разобрался. Такую прокрутку реализовать нельзя из-за невозможности частичного скрытия любых объектов чарта (в данном случае это ClientArea и кнопки). В других UI это реализовано по-другому. Там выводится прямоугольная область которая попадает в Area скроллинга, а всё остальное скрыто. Отсюда и возникла путаница.