English 中文 Español Deutsch 日本語 Português
preview
Как создать графическую панель любой сложности и как это работает

Как создать графическую панель любой сложности и как это работает

MetaTrader 5Примеры | 2 апреля 2018, 16:40
11 650 57
Vladimir Karputov
Vladimir Karputov

Содержание

Введение

До сих пор большинство программистов, которые пишут индикаторы и советники для платформы MetaTrader 5, почти не пользуются возможностями по созданию графических интерфейсов в своих приложениях.  Основная причина такого положения дел, на мой взгляд, заключается в том, что классы Панели и диалоги из Стандартной библиотеки содержат только сухое описание методов. Да, для многих графических контролов в справке даны примеры кода с комментариями. Но без полного понимания устройства и идеологии, заложенной в них, нельзя создавать собственные панели.

Я постарался разобраться, как и что в них устроено, и хочу поделиться с остальными разработчиками теми знаниями, которые мне удалось самостоятельно получить. Начал я с простого приложения, которое создает графическую панель на основе класса CAppDialog: последовательными шагами вносил в него правки и разбирался в полученных результатах.

Из этой статьи вы узнаете всё необходимое о классе CAppDialog: как создать панель и какой минимальный набор функций нужно прописать, как добавить в нее дополнительные элементы (например, кнопки). Мы посмотрим, из каких объектов состоит панель и каков порядок их создания. Также я покажу, какие константы используются при создании панели и как их изменить. Нелишней будет и информация о том, как можно быстро сломать любую панель.


Создаем панель на основе CAppDialog

Для начала — немного справочной информации. 

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

Вот минимальный код, который создаёт панель:

//+------------------------------------------------------------------+
//|                                              LearnCAppDialog.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Controls\Dialog.mqh>

CAppDialog AppWindow;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create application dialog
   if(!AppWindow.Create(0,"AppWindow",0,20,20,360,324))
      return(INIT_FAILED);
//--- run application
   AppWindow.Run();
//--- succeed
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 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
  {
   AppWindow.ChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

Результат работы советника "LearnCAppDialog.mq5" — созданная панель управления:

CAppDialog panel

Советник "LearnCAppDialog.mq5" содержит минимальный набор команд, необходимый для создания и функционирования панели. Итак, по шагам:

  • объявляем на глобальном програмном уровне экземпляр класса CAppDialog:
#include <Controls\Dialog.mqh>

CAppDialog AppWindow;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
  • создаём панель AppWindow  и запускаем панель в работу:
int OnInit()
  {
//--- create application dialog
   if(!AppWindow.Create(0,"AppWindow",0,20,20,360,324))
      return(INIT_FAILED);
//--- run application
   AppWindow.Run();
//--- succeed
   return(INIT_SUCCEEDED);
  }
  • передаём события ChartEvent в панель& AppWindow :
//+------------------------------------------------------------------+
//| 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
  {
   AppWindow.ChartEvent(id,lparam,dparam,sparam);
  }

и последнее, очень важное:

  • уничтожаем элемент управления — вызываем метод Destroy
void OnDeinit(const int reason)
  {
//--- destroy dialog
   AppWindow.Destroy(reason);
  }

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


Что умеет AppWindow

Теоретически, панель на базе класса CAppDialog может обрабатывать такие события: 

//+------------------------------------------------------------------+
//| Events                                                           |
//+------------------------------------------------------------------+
#define ON_CLICK                (0)   // clicking on control event
#define ON_DBL_CLICK            (1)   // double clicking on control event
#define ON_SHOW                 (2)   // showing control event
#define ON_HIDE                 (3)   // hiding control event
#define ON_CHANGE               (4)   // changing control event
#define ON_START_EDIT           (5)   // start of editing event
#define ON_END_EDIT             (6)   // end of editing event
#define ON_SCROLL_INC           (7)   // increment of scrollbar event
#define ON_SCROLL_DEC           (8)   // decrement of scrollbar event
#define ON_MOUSE_FOCUS_SET      (9)   // the "mouse cursor entered the control" event
#define ON_MOUSE_FOCUS_KILL     (10)  // the "mouse cursor exited the control" event
#define ON_DRAG_START           (11)  // the "control dragging start" event
#define ON_DRAG_PROCESS         (12)  // the "control is being dragged" event
#define ON_DRAG_END             (13)  // the "control dragging end" event
#define ON_BRING_TO_TOP         (14)  // the "mouse events priority increase" event
#define ON_APP_CLOSE            (100) // "closing the application" event

Эти события взяты из блока Events файла [data folder]\MQL5\Include\Controls\Defines.mqh. Это клик, двойной клик, начало и окончание редактирования, получение фокуса, перетаскивание (начало, в процессе и окончание), показ и скрытие панели. Примеры работы с этими событиями можно посмотреть в примерах раздела Панели и диалоги. Например, в примере  CRadioGroup идёт обработка события "ON_CHANGE", а в примере CScrollV — обработка событий "ON_SCROLL_INC" и "ON_SCROLL_DEC".


Структура объекта CAppDialog

Если запустить советник "LearnCAppDialog.mq5" на пустом графике, то по нажатию "Ctrl"+"B" и клику на кнопке "Все" можно увидеть все объекты, из которых состоит панель:


Порядок создания и наложения объектов из раздела Панели и диалоги Стандартной библиотеки выглядит следующим образом. Сначала создается объект "Border", в его границах добавляется фон панели в виде объекта "Back", на фон накладывается клиентская область "ClientBack", внутри которой могут находится дочерние контролы. В верхнюю часть панели добавляются объект "Caption" с названием панели и две кнопки для управления.

Разложение панели AppWindow на графические контролы

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

Объект "Border" — это OBJ_RECTANGLE_LABEL у которого (по умолчанию для всех панелей) устанавливается белый цвет рамки. То есть задача объекта "Border" — чисто эстетическая: отобразить белую рамку, при этом само тело объекта "Border" будет скрыто объектом "Back".



Схема наследования объектов

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


В советнике "LearnCAppDialog.mq5" панель AppWindow состоит из шести объектов, каждый из которых выполняет свою задачу.


Панель на базе CAppDialog может быть создана как из эксперта, так и из индикатора. При этом создание панели отличается, в зависимости от того, какой тип программы (эксперт или индикатор) создаёт панель и в каком подокне работает программа:

  • если программа является экспертом (тип запущенной программы PROGRAM_EXPERT) — тогда панель создаётся ТОЛЬКО в главном окне (номер окна "0") и только при помощи метода CAppDialog::CreateExpert;
  • если программа является индикатором (тип запущенной программы PROGRAM_INDICATOR) — тогда проверяется номер окна, в котором запущена программа:
    • если это главное окно (номер окна "0"), то панель создаётся при помощи метода CAppDialog::CreateIndicator;
    • если это подокно — панель создаётся при помощи метода CAppDialog::CreateExpert.

Особенность метода CAppDialog::CreateIndicator в том, что при создании панель автоматически:

  • подстраивается под ширину окна;
  • подстраивает под себя высоту окна.

Пример панели-индикатора [data folder]\MQL5\Indicators\Examples\Panels\SimplePanel\SimplePanel.mq5 после создания и после минимизации:


"CreateExpert" создаёт панель в главном окне (номер окна равен "0"), и подразумевается, что программа, создающая панель, является советником.

Есть исключение из этих правил: можно из индикатора создать панель в главном окне. В таком случае будет применён метод создания панели "CreateIndicator".


Где заложены основные константы для создания объектов и как их переопределить через #undef

Код будет реализован в советнике "AppWindowEditDefine.mq5".

Основные константы панели и элементов управления расположены в файле [data folder]\MQL5\Include\Controls\Defines.mqh, который подключается в классе CWnd:

//+------------------------------------------------------------------+
//|                                                          Wnd.mqh |
//|                   Copyright 2009-2017, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Rect.mqh"
#include "Defines.mqh"
#include <Object.mqh>
class CDragWnd;

Напомню иерархию наследования:

  • CWnd
    • CWndContainer
      • CDialog
        • CAppDialog

Особенно нас будет интересовать вот эта группа констант:

//+------------------------------------------------------------------+
//| Drawing styles and colors                                        |
//+------------------------------------------------------------------+
//--- common
#define CONTROLS_FONT_NAME                  "Trebuchet MS"
#define CONTROLS_FONT_SIZE                  (10)
//--- Text
#define CONTROLS_COLOR_TEXT                 C'0x3B,0x29,0x28'
#define CONTROLS_COLOR_TEXT_SEL             White
#define CONTROLS_COLOR_BG                   White
#define CONTROLS_COLOR_BG_SEL               C'0x33,0x99,0xFF'
//--- Button
#define CONTROLS_BUTTON_COLOR               C'0x3B,0x29,0x28'
#define CONTROLS_BUTTON_COLOR_BG            C'0xDD,0xE2,0xEB'
#define CONTROLS_BUTTON_COLOR_BORDER        C'0xB2,0xC3,0xCF'
//--- Label
#define CONTROLS_LABEL_COLOR                C'0x3B,0x29,0x28'
//--- Edit
#define CONTROLS_EDIT_COLOR                 C'0x3B,0x29,0x28'
#define CONTROLS_EDIT_COLOR_BG              White
#define CONTROLS_EDIT_COLOR_BORDER          C'0xB2,0xC3,0xCF'
//--- Scrolls
#define CONTROLS_SCROLL_COLOR_BG            C'0xEC,0xEC,0xEC'
#define CONTROLS_SCROLL_COLOR_BORDER        C'0xD3,0xD3,0xD3'
//--- Client
#define CONTROLS_CLIENT_COLOR_BG            C'0xDE,0xDE,0xDE'
#define CONTROLS_CLIENT_COLOR_BORDER        C'0x2C,0x2C,0x2C'
//--- ListView
#define CONTROLS_LISTITEM_COLOR_TEXT        C'0x3B,0x29,0x28'
#define CONTROLS_LISTITEM_COLOR_TEXT_SEL    White
#define CONTROLS_LISTITEM_COLOR_BG          White
#define CONTROLS_LISTITEM_COLOR_BG_SEL      C'0x33,0x99,0xFF'
#define CONTROLS_LIST_COLOR_BG              White
#define CONTROLS_LIST_COLOR_BORDER          C'0xB2,0xC3,0xCF'
//--- CheckGroup
#define CONTROLS_CHECKGROUP_COLOR_BG        C'0xF7,0xF7,0xF7'
#define CONTROLS_CHECKGROUP_COLOR_BORDER    C'0xB2,0xC3,0xCF'
//--- RadioGroup
#define CONTROLS_RADIOGROUP_COLOR_BG        C'0xF7,0xF7,0xF7'
#define CONTROLS_RADIOGROUP_COLOR_BORDER    C'0xB2,0xC3,0xCF'
//--- Dialog
#define CONTROLS_DIALOG_COLOR_BORDER_LIGHT  White
#define CONTROLS_DIALOG_COLOR_BORDER_DARK   C'0xB6,0xB6,0xB6'
#define CONTROLS_DIALOG_COLOR_BG            C'0xF0,0xF0,0xF0'
#define CONTROLS_DIALOG_COLOR_CAPTION_TEXT  C'0x28,0x29,0x3B'
#define CONTROLS_DIALOG_COLOR_CLIENT_BG     C'0xF7,0xF7,0xF7'
#define CONTROLS_DIALOG_COLOR_CLIENT_BORDER C'0xC8,0xC8,0xC8'

Чтобы изменить эти макроподстановки нужно примерить директиву #undef:

Директива #undef предназначена для отмены макроса, объявленного ранее.

Таким образом, получается следующий алгоритм: отменяем макрос, объявленный ранее; снова объявляем макрос, только уже с изменённым параметром. Для этого нужно выполнить следующую хитрость: подключить файл "Defines.mqh" ПЕРЕД "Dialog.mqh":

//+------------------------------------------------------------------+
//|                                          AppWindowEditDefine.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.001"
#property description "Control Panels and Dialogs. Demonstration class CBmpButton"
#include <Controls\Defines.mqh>

после подключения "Defines.mqh" отменяем  макросы:

#undef CONTROLS_FONT_NAME 
#undef CONTROLS_FONT_SIZE 

#undef CONTROLS_BUTTON_COLOR
#undef CONTROLS_BUTTON_COLOR_BG
#undef CONTROLS_BUTTON_COLOR_BORDER

#undef CONTROLS_DIALOG_COLOR_BORDER_LIGHT
#undef CONTROLS_DIALOG_COLOR_BORDER_DARK
#undef CONTROLS_DIALOG_COLOR_BG
#undef CONTROLS_DIALOG_COLOR_CAPTION_TEXT
#undef CONTROLS_DIALOG_COLOR_CLIENT_BG
#undef CONTROLS_DIALOG_COLOR_CLIENT_BORDER 

прописываем входные параметры:

input string   font_name                  = "Trebuchet MS";
input int      font_size                  = 10;

input color    button_color               = C'0x3B,0x29,0x28';
input color    button_color_bg            = C'0xDD,0xE2,0xEB';
input color    button_color_border        = C'0xB2,0xC3,0xCF';

input color    dialog_color_border_light  = White;
input color    dialog_color_border_dark   = C'0xB6,0xB6,0xB6';
input color    dialog_color_bg            = C'0xF0,0xF0,0xF0';
input color    dialog_color_caption_text  = C'0x28,0x29,0x3B';
input color    dialog_color_client_bg     = C'0xF7,0xF7,0xF7';
input color    dialog_color_client_border = C'0xC8,0xC8,0xC8';

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

#define CONTROLS_FONT_NAME                font_name
#define CONTROLS_FONT_SIZE                font_size

#define CONTROLS_BUTTON_COLOR             button_color
#define CONTROLS_BUTTON_COLOR_BG          button_color_bg
#define CONTROLS_BUTTON_COLOR_BORDER      button_color_border

#define CONTROLS_DIALOG_COLOR_BORDER_LIGHT dialog_color_border_light
#define CONTROLS_DIALOG_COLOR_BORDER_DARK dialog_color_border_dark
#define CONTROLS_DIALOG_COLOR_BG          dialog_color_bg
#define CONTROLS_DIALOG_COLOR_CAPTION_TEXT dialog_color_caption_text
#define CONTROLS_DIALOG_COLOR_CLIENT_BG   dialog_color_client_bg
#define CONTROLS_DIALOG_COLOR_CLIENT_BORDER dialog_color_client_border

#include <Controls\Dialog.mqh>
#include <Controls\BmpButton.mqh>

Пример работы:



Еще раз закрепляем, как устроен CAppDialog

Наша панель — это объект класса CAppDialog. Он унаследовал от класс CWndContainer метод ControlsTotal (количество элементов управления в контейнере). Это даёт возможность в цикле обойти все элементы управления панели и что-нибудь с ними сделать. А объявлены эти элементы в области private родительского класса CDialog:

//+------------------------------------------------------------------+
//| 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:

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

//+------------------------------------------------------------------+
//| Create a control                                                 |
//+------------------------------------------------------------------+
bool CDialog::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 parent class
   if(!CWndContainer::Create(chart,name,subwin,x1,y1,x2,y2))
      return(false);
//--- create dependent controls
   if(!m_panel_flag && !CreateWhiteBorder())
      return(false);
   if(!CreateBackground())
      return(false);
   if(!CreateCaption())
      return(false);
   if(!CreateButtonClose())
      return(false);
   if(!CreateClientArea())
      return(false);

и, главное, какие им присваиваются имена: m_white_border -> "29437Border", m_background -> "29437Back", m_caption -> "29437Caption", m_button_close -> "29437Close", m_client_area -> "29437Client". В этих именах число "29437" — идентификатор панели на время её жизни.

Таким образом, можно для элементов панели изменить некоторые свойства. Например, изменим цвет для объектов "m_client_area" и "m_background":

//+------------------------------------------------------------------+
//|                                            LearnCAppDialog_1.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Controls\Dialog.mqh>

CAppDialog AppWindow;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create application dialog
   if(!AppWindow.Create(0,"AppWindow",0,20,20,360,324))
      return(INIT_FAILED);
//---
   int total=AppWindow.ControlsTotal();
   CWndClient*myclient;
   for(int i=0;i<total;i++)
     {
      CWnd*obj=AppWindow.Control(i);
      string name=obj.Name();
      PrintFormat("%d is %s",i,name);
      //--- color 
      if(StringFind(name,"Client")>0)
        {
         CWndClient *client=(CWndClient*)obj;
         client.ColorBackground(clrRed);
         myclient=client;
         Print("client.ColorBackground(clrRed);");
         ChartRedraw();
        }
      //---
      if(StringFind(name,"Back")>0)
        {
         CPanel *panel=(CPanel*) obj;
         panel.ColorBackground(clrGreen);
         Print("panel.ColorBackground(clrGreen);");
         ChartRedraw();
        }
     }
   AppWindow.Delete(myclient);
//--- run application
   AppWindow.Run();
//--- succeed
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 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
  {
   AppWindow.ChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

Обратите внимание на строку: в ней вызывается метод CWndContainer::Delete — удаление элемента из группы (контейнера). После того, как элемент "m_client_area" удален из группы, при перемещении панели команда на перемещение не будет передаваться объекту  "m_client_area". Клиентская область останется на месте:


Но, тем не менее, при закрытии панели элемент "m_client_area" будет удалён с графика вместе с другими ее элементами.

А теперь тот же пример, только вместо метода CWndContainer::Delete применим метод CWndContainer::Destroy — уничтожим объект "m_client_area":

//+------------------------------------------------------------------+
//|                                            LearnCAppDialog_2.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Controls\Dialog.mqh>

CAppDialog AppWindow;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create application dialog
   if(!AppWindow.Create(0,"AppWindow",0,20,20,360,324))
      return(INIT_FAILED);
//---
   int total=AppWindow.ControlsTotal();
   CWndClient*myclient;
   for(int i=0;i<total;i++)
     {
      CWnd*obj=AppWindow.Control(i);
      string name=obj.Name();
      PrintFormat("%d is %s",i,name);
      //--- color 
      if(StringFind(name,"Client")>0)
        {
         CWndClient *client=(CWndClient*)obj;
         client.ColorBackground(clrRed);
         myclient=client;
         Print("client.ColorBackground(clrRed);");
         ChartRedraw();
        }
      //---
      if(StringFind(name,"Back")>0)
        {
         CPanel *panel=(CPanel*) obj;
         panel.ColorBackground(clrGreen);
         Print("panel.ColorBackground(clrGreen);");
         ChartRedraw();
        }
     }
   Sleep(5000);
   myclient.Destroy();
//--- run application
   AppWindow.Run();
//--- succeed
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 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
  {
   AppWindow.ChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

Вот как это выглядит: 5 секунд сна после создания панели, после чего клиентская область уничтожается:



Как добавить новые элементы управления — две кнопки

Модифицируем советник из раздела "Создаем панель на основе CAppDialog": добавим на панель две кнопки на базе класса CButton и сохраним его под именем "AppWindowTwoButtons.mq5". Перед добавлением самих кнопок (как, впрочем, и при проектировании любых панелей) нужно предварительно представить, какие размеры будут иметь кнопки и где они будут расположены. Допустим, что рисунок ниже и есть та самая панель с кнопками, которую мы будем строить: 


Здесь:

  • "TOP" — отступ от верхней границы клиентской области (за этот размер будет отвечать константа "INDENT_TOP");
  • "LEFT" — отступ от левой границы клиентской области (за этот размер будет отвечать константа "INDENT_LEFT");
  • "HEIGHT" — высота кнопки (за этот размер будет отвечать константа "BUTTON_HEIGHT");
  • "WIDTH" — ширина кнопки (за этот размер будет отвечать константа "BUTTON_WIDTH").

Также понадобится ещё одна константа — минимальный отступ по горизонтали между элементами управления. Назовём его "CONTROLS_GAP_X". 

Для использования класса CButton этот класс сначала нужно подключить:

//+------------------------------------------------------------------+
//|                                          AppWindowTwoButtons.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.001"
#property description "Control Panels and Dialogs. Demonstration class CButton"
#include <Controls\Dialog.mqh>
#include <Controls\Button.mqh>

Далее прописываем константы размеров и расположения кнопок:

//+------------------------------------------------------------------+
//| 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
//---

Объявляем на глобальном програмном уровне два экземпляра класса CButton:

#define BUTTON_HEIGHT                       (20)      // size by Y coordinate
//---
CAppDialog           AppWindow;
CButton              m_button1;                       // the button object
CButton              m_button2;                       // the button object
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()

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

OnInit() изменится незначительно — будут добавлены вызовы и проверки результатов создания кнопок:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create application dialog
   if(!AppWindow.Create(0,"AppWindow with Two Buttons",0,40,40,380,344))
      return(INIT_FAILED);
//--- create dependent controls
   if(!CreateButton1())
      return(false);
   if(!CreateButton2())
      return(false);
//--- run application
   AppWindow.Run();
//--- succeed
   return(INIT_SUCCEEDED);
  }


На примере "CreateButton1()" подробнее остановимся на процессе создания и ПРИВЯЗЫВАНИЯ кнопки к панели.

От класса CButton будем использовать метод "Create" — создание кнопки:


 и метод "Text" — устанавливаем надпись на кнопке (причём метод "Text" унаследован от класса CWndObj) :


На данном этапе кнопка создана, но существует отдельно от панели. Для того, чтобы их объединить, нужно выполнить метод CDialog::Add — добавить кнопку в клиентскую область панели:

   if(!AppWindow.Add(m_button1))
      return(false);
//--- succeed
   return(true);
  }

И теперь код создания кнопки полностью:

//+------------------------------------------------------------------+
//| Create the "Button1" button                                      |
//+------------------------------------------------------------------+
bool 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(!AppWindow.Add(m_button1))
      return(false);
//--- succeed
   return(true);
  }

Не забываем о том, что в OnDeinit() нужно уничтожить панель, а в OnChartEvent() — все события передавать в форму:

//+------------------------------------------------------------------+
//| 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
  {
   AppWindow.ChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+
//| Create the "Button1" button                                      |
//+------------------------------------------------------------------+

Как происходит перемещение вложенных контролов и их отрисовка

Напомню, что панель AppWindow — объект класса CAppDialog, который является потомком CDialog. В свою очередь, CDialog наследуется от CWndContainer:

Класс CWndContainer — базовый класс для группы элементов управления Стандартной библиотеки.

То есть родительский класс CWndContainer управляет перемещением всей группы элементов управления, входящих в панель.

Перемещение всех элементов управления панели производится в "CWndContainer::Shift" в цикле.

//+------------------------------------------------------------------+
//| Relative movement of the controls group                          |
//+------------------------------------------------------------------+
bool CWndContainer::Shift(const int dx,const int dy)
  {
//--- call of the method of the parent class
   if(!CWnd::Shift(dx,dy))
      return(false);
//--- loop by elements of group
   int total=m_controls.Total();
   for(int i=0;i<total;i++)
     {
      CWnd *control=Control(i);
      //--- check of pointer
      if(control==NULL)
         continue;
      //--- move the group item
      control.Shift(dx,dy);
     }
//--- succeed
   return(true);
  }

На примере панели из справки CBmpButton (у меня данный пример лежит в папке \MQL5\Experts\MyExp\Help\With the Panel. EN\ControlsBmpButton.mq5).

В метод "CWndContainer::Shift" попадаем так:



Добавим в CAppDialog группу элементов с помощью класса CDialog

Выше был показан пример панели с двумя кнопками. Помните, я там говорил, что объявление кнопок на глобальном программном уровне — нехороший пример? Рассмотрим более правильный пример: весь код создания панели и кнопок разместим в классе-наследнике от CAppDialog. Пример создания панели: "AppWindowTwoButtonsClass.mq5".

Класс "CAppWindowTwoButtons" — наследник CAppDialog, содержит следующие методы:

 Создание
 Create  Создание главного элемента управления — панель
 CreateButton1  Создание подчинённого элемента управления — кнопка #1
 CreateButton2  Создание подчинённого элемента управления — кнопка #2

Код "AppWindowTwoButtonsClass.mq5": цветом выделен код, который теперь находится в классе:

//+------------------------------------------------------------------+
//|                                     AppWindowTwoButtonsClass.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 "Control Panels and Dialogs. Demonstration class CButton"
#include <Controls\Dialog.mqh>
#include <Controls\Button.mqh>
//+------------------------------------------------------------------+
//| 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 CAppWindowTwoButtons                                       |
//| Usage: main dialog of the Controls application                   |
//+------------------------------------------------------------------+
class CAppWindowTwoButtons : public CAppDialog
  {
private:
   CButton           m_button1;                       // the button object
   CButton           m_button2;                       // the button object

public:
                     CAppWindowTwoButtons(void);
                    ~CAppWindowTwoButtons(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);

protected:
   //--- create dependent controls
   bool              CreateButton1(void);
   bool              CreateButton2(void);

  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CAppWindowTwoButtons::CAppWindowTwoButtons(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CAppWindowTwoButtons::~CAppWindowTwoButtons(void)
  {
  }
//+------------------------------------------------------------------+
//| Create                                                           |
//+------------------------------------------------------------------+
bool CAppWindowTwoButtons::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2)
  {
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
      return(false);
//--- create dependent controls
   if(!CreateButton1())
      return(false);
   if(!CreateButton2())
      return(false);
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Global Variable                                                  |
//+------------------------------------------------------------------+
CAppWindowTwoButtons ExtDialog;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create application dialog
   if(!ExtDialog.Create(0,"AppWindowClass with Two Buttons",0,40,40,380,344))
      return(INIT_FAILED);
//--- run application
   ExtDialog.Run();
//--- succeed
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   Comment("");
//--- destroy dialog
   ExtDialog.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
  {
   ExtDialog.ChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+
//| Create the "Button1" button                                      |
//+------------------------------------------------------------------+
bool CAppWindowTwoButtons::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);
  }
//+------------------------------------------------------------------+
//| Create the "Button2"                                             |
//+------------------------------------------------------------------+
bool CAppWindowTwoButtons::CreateButton2(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,"Button2",0,x1,y1,x2,y2))
      return(false);
   if(!m_button2.Text("Button2"))
      return(false);
   if(!Add(m_button2))
      return(false);
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+

На основе примера "AppWindowTwoButtonsClass.mq5" рассмотрим  алгоритм создания панели и элементов управления. Все действия происходят в функции CAppWindowTwoButtons::Create.

  • Создаётся сама панель:
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
      return(false);
  • Создаются подчинённые элементы управления:
//--- create dependent controls
   if(!CreateButton1())
      return(false);
   if(!CreateButton2())
      return(false);
  • И самое главное: когда кнопка создана, она ещё не является подчинённым элементом нашей панели, а существует сама по себе. Чтобы сделать ее одним из подчинённых элементов панели, надо вызвать метод Add (вызывается метод CDialog::Add — элемент управления добавляется в клиентскую область по указателю/ссылке)
...
   if(!Add(m_button1))
      return(false);
...
   if(!Add(m_button2))
      return(false);
...

После добавления элемент управления становится подчинённым панели: все события теперь будут распределяться централизованно, от панели к подчинённым элементам управления.

Как переопределить поведение стандартных элементов

Если свернуть панель, то она будет всегда позиционироваться в координате (10;10). При этом свёрнутая панель частично перекрывается кнопками быстрой торговли:

Исправим такое позиционирование, а заодно будем учитывать, развёрнута ли панель быстрой торговли. Для этого нужно переопределить родительский метод CAppDialog::Minimize. На основе кода "AppWindowTwoButtons.mq5" из главы "Добавим в CAppDialog группу элементов с помощью класса CDialog" создадим пример "AppWindowCorrectMinimization.mq5"

Изменения: объявляем метод "Minimize":

protected:
   //--- create dependent controls
   bool              CreateButton1(void);
   bool              CreateButton2(void);
   //--- override the parent method
   virtual void      Minimize(void);

  };

и прописываем тело метода:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CAppWindowCorrectMinimization::Minimize(void)
  {
//--- переменная для получения панели быстрой торговли
   long one_click_visible=-1;  // 0 - панели быстрой торговли нет 
   if(!ChartGetInteger(m_chart_id,CHART_SHOW_ONE_CLICK,0,one_click_visible))
     {
      //--- выведем сообщение об ошибке в журнал "Эксперты"
      Print(__FUNCTION__+", Error Code = ",GetLastError());
     }
//--- минимальный отступ для свернутой панели приложения
   int min_y_indent=28;
   if(one_click_visible)
      min_y_indent=100;  // отступ, если быстрая торговля показана на графике
//--- получим текущий отступ для свернутой панели приложения
   int current_y_top=m_min_rect.top;
   int current_y_bottom=m_min_rect.bottom;
   int height=current_y_bottom-current_y_top;
//--- вычислим новый минимальный отступ от верха для свернутой панели приложения
   if(m_min_rect.top!=min_y_indent)
     {
      m_min_rect.top=min_y_indent;
      //--- сместим также нижнюю границу свернутой иконки
      m_min_rect.bottom=m_min_rect.top+height;
     }
//--- теперь можно вызвать метод базового класса
   CAppDialog::Minimize();
  }


Как читать встроенные макросы вида обработки событий

Панель может обрабатывать следующие виды событий (взято из [date folder]\MQL5\Include\Controls\Defines.mqh" в блок "Events")

//+------------------------------------------------------------------+
//| Events                                                           |
//+------------------------------------------------------------------+
#define ON_CLICK                (0)   // clicking on control event
#define ON_DBL_CLICK            (1)   // double clicking on control event
#define ON_SHOW                 (2)   // showing control event
#define ON_HIDE                 (3)   // hiding control event
#define ON_CHANGE               (4)   // changing control event
#define ON_START_EDIT           (5)   // start of editing event
#define ON_END_EDIT             (6)   // end of editing event
#define ON_SCROLL_INC           (7)   // increment of scrollbar event
#define ON_SCROLL_DEC           (8)   // decrement of scrollbar event
#define ON_MOUSE_FOCUS_SET      (9)   // the "mouse cursor entered the control" event
#define ON_MOUSE_FOCUS_KILL     (10)  // the "mouse cursor exited the control" event
#define ON_DRAG_START           (11)  // the "control dragging start" event
#define ON_DRAG_PROCESS         (12)  // the "control is being dragged" event
#define ON_DRAG_END             (13)  // the "control dragging end" event
#define ON_BRING_TO_TOP         (14)  // the "mouse events priority increase" event
#define ON_APP_CLOSE            (100) // "closing the application" event

Эти события обрабатываются в методе CAppDialog::OnEvent. Для облегчения визуального восприятия различных видов событий в [date folder]\MQL5\Include\Controls\Defines.mqh" в блоке "Macro of event handling map" описаны несколько макросов:

//+------------------------------------------------------------------+
//| Macro of event handling map                                      |
//+------------------------------------------------------------------+
#define INTERNAL_EVENT                           (-1)
//--- beginning of map
#define EVENT_MAP_BEGIN(class_name)              bool class_name::OnEvent(const int id,const long& lparam,const double& dparam,const string& sparam) {
//--- end of map
#define EVENT_MAP_END(parent_class_name)         return(parent_class_name::OnEvent(id,lparam,dparam,sparam)); }
//--- event handling by numeric ID
#define ON_EVENT(event,control,handler)          if(id==(event+CHARTEVENT_CUSTOM) && lparam==control.Id()) { handler(); return(true); }
//--- event handling by numeric ID by pointer of control
#define ON_EVENT_PTR(event,control,handler)      if(control!=NULL && id==(event+CHARTEVENT_CUSTOM) && lparam==control.Id()) { handler(); return(true); }
//--- event handling without ID analysis
#define ON_NO_ID_EVENT(event,handler)            if(id==(event+CHARTEVENT_CUSTOM)) { return(handler()); }
//--- event handling by row ID
#define ON_NAMED_EVENT(event,control,handler)    if(id==(event+CHARTEVENT_CUSTOM) && sparam==control.Name()) { handler(); return(true); }
//--- handling of indexed event
#define ON_INDEXED_EVENT(event,controls,handler) { int total=ArraySize(controls); for(int i=0;i<total;i++) if(id==(event+CHARTEVENT_CUSTOM) && lparam==controls[i].Id()) return(handler(i)); }
//--- handling of external event
#define ON_EXTERNAL_EVENT(event,handler)         if(id==(event+CHARTEVENT_CUSTOM)) { handler(lparam,dparam,sparam); return(true); }

Благодаря макросам из блока "Events" и из блока "Macro of event handling map", метод OnEvent в панели принимает такой вид: 

//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CControlsDialog)
ON_EVENT(ON_CLICK,m_bmpbutton1,OnClickBmpButton1)
ON_EVENT(ON_CLICK,m_bmpbutton2,OnClickBmpButton2)
EVENT_MAP_END(CAppDialog)

Напомню, что данный код взят из справки CBmpButton, и здесь "CControlsDialog" — экземпляр класса CAppDialog, панель в виде класса. 

Учитывая макросы из блока "Macro of event handling map", метод OnEvent будет таким:

bool CControlsDialog::OnEvent(const int id,const long& lparam,const double& dparam,const string& sparam) {
if(id==(ON_CLICK+CHARTEVENT_CUSTOM) && lparam==m_bmpbutton1.Id()) { OnClickBmpButton1(); return(true); }
if(id==(ON_CLICK+CHARTEVENT_CUSTOM) && lparam==m_bmpbutton2.Id()) { OnClickBmpButton2(); return(true); }
return(CAppDialog::OnEvent(id,lparam,dparam,sparam)); }

и после применения стилизатора:

bool CControlsDialog::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==(ON_CLICK+CHARTEVENT_CUSTOM) && lparam==m_bmpbutton1.Id())
     {
      OnClickBmpButton1();
      return(true);
     }
   if(id==(ON_CLICK+CHARTEVENT_CUSTOM) && lparam==m_bmpbutton2.Id())
     {
      OnClickBmpButton2();
      return(true);
     }
   return(CAppDialog::OnEvent(id,lparam,dparam,sparam));
  }

Полученный код можно прочесть так: если получено пользовательское событие клик по элементу "m_bmpbutton1" — тогда будет вызван метод OnClickBmpButton1(). Если же получено пользовательское событие клик по элементу "m_bmpbutton2" — тогда будет вызван метод OnClickBmpButton2().

Пример обработки событий

На базе "AppWindowTwoButtonsClass.mq5" создадим "AppWindowTwoButtonsClasssEvents.mq5" — будут добавлены обработчики событий клика по кнопке.

Шаг первый — объявляем OnEvent, а также OnClickButton1 и OnClickButton2.

//+------------------------------------------------------------------+
//| Class CControlsDialog                                            |
//| Usage: main dialog of the Controls application                   |
//+------------------------------------------------------------------+
class CAppWindowTwoButtons : public CAppDialog
  {
private:
   CButton           m_button1;                       // the button object
   CButton           m_button2;                       // the button object

public:
                     CAppWindowTwoButtons(void);
                    ~CAppWindowTwoButtons(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);

  };

Шаг второй: метод OnEvent, который благодаря макросам из блока "Events" и из блока "Macro of event handling map" файла [date folder]\MQL5\Include\Controls\Defines.mqh" принимает такой вид: 

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(CAppWindowTwoButtons)
ON_EVENT(ON_CLICK,m_button1,OnClickButton1)
ON_EVENT(ON_CLICK,m_button2,OnClickButton2)
EVENT_MAP_END(CAppDialog)
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+

Теперь нужно написать тело функций OnClickButton1 и OnClickButton2. Пусть клик по кнопке 1 будет отвечать за открытие BUY позиции, а клик по кнопке 2 — за закрытие позиции. 

Для этого сначала изменим надпись на кнопках (изменения, соответственно, происходят в CreateButton1 и CreateButton2):

...
   if(!m_button1.Text("Open BUY"))
      return(false);
...
...
   if(!m_button2.Text("Close"))
      return(false);
...

Теперь определимся, какие классы нужно подключить: для торговли нужен CTrade, для работы с позициями — CPositionInfo, а тип торгового счёта будем получать из CAccountInfo:

#property description "Control Panels and Dialogs. Demonstration class CButton"
#include <Controls\Dialog.mqh>
#include <Controls\Button.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\Trade.mqh>
#include <Trade\AccountInfo.mqh>
//+------------------------------------------------------------------+
//| defines                                                          |

Чтобы работать с этими классами, объявим в секции protected нашей панели экземпляры этих классов:

class CAppWindowTwoButtons : public CAppDialog
  {
protected:
   CPositionInfo     m_position;                      // trade position object
   CTrade            m_trade;                         // trading object
   CAccountInfo      m_account;                       // account info wrapper

private:
   CButton           m_button1;                       // the button object

Методы обработки кликов:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CAppWindowTwoButtons::OnClickButton1(void)
  {
   if(m_account.TradeMode()==ACCOUNT_TRADE_MODE_DEMO)
      m_trade.Buy(1.0);
  }
//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CAppWindowTwoButtons::OnClickButton2(void)
  {
   if(m_account.TradeMode()==ACCOUNT_TRADE_MODE_DEMO)
      for(int i=PositionsTotal()-1;i>=0;i--) // returns the number of current positions
         if(m_position.SelectByIndex(i)) // selects the position by index for further access to its properties
            if(m_position.Symbol()==Symbol())
               m_trade.PositionClose(m_position.Ticket()); // close a position by the specified symbol
  }

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


Попробуйте создать панель самостоятельно — это несложно!

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

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

Надеюсь, что приведённые примеры помогут вам при создании панелей на базе CAppDialog. Также рекомендую изучить отдельные примеры создания элементов управления, приведенные в  разделе Панели и диалоги.

Наименование файлаКомментарий
 LearnCAppDialog.mq5 Минимальный код панели на базе класса CAppDialog
 AppWindowEditDefine.mq5 Советник-панель, которая переопределяет константы из Defines.mqh
 LearnCAppDialog_1.mq5 Изменяет цвет для объектов "m_client_area" и "m_background"
 LearnCAppDialog_2.mq5 Вместо метода CWndContainer::Delete применим метод CWndContainer::Destroy — уничтожим объект "m_client_area"
 AppWindowTwoButtons.mq5 Панель с добавленными двумя кнопками
 AppWindowTwoButtonsClass.mq5 Панель, с добавленными двумя кнопками, в виде класса
 AppWindowCorrectMinimization.mq5 Пример исправления позиционирования панели по умолчанию
 AppWindowTwoButtonsClasssEvents.mq5 Панель, с добавленными двумя кнопками, в виде класса. Обработка событий кнопок


Прикрепленные файлы |
MQL5.zip (13.66 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (57)
vvebus
vvebus | 27 окт. 2022 в 18:57
ON_MOUSE_FOCUS_KILL судя по всему никак не используется. При добавлении в Map ничего не происходит. Так же можно закомментировать ON_MOUSE_FOCUS_KILL и компилятор пропустит. 
Vitaliy Davydov
Vitaliy Davydov | 22 дек. 2022 в 13:23

Зравствуйте!

Создаю панель унаследованную от CDialog (либо от CWndContainer, без разницы).

Если создаю в основном окне графика, то все работает - панель перемещается мышью и закрывается кнопкой закрытия.

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

Вопрос - как заставить панель нормально реагировать на события графика  в подокне?

Vitaliy Davydov
Vitaliy Davydov | 23 мар. 2023 в 10:50
Vasiliy Pushkaryov #:
Пару лет назад выкладывал работающее решение с кнопками. Но поля Edit в тестере МТ5 не работают. Хотя можно добавить рядом с полем Edit маленькие кнопки +/- и править через них. Но там свои нюансы будут.

Добрый день!

Спасибо, разобрался.

Теперь другие проблемы - все эти панели из стандартной библиотеки,

абсолютно нефункциональны в тестере МТ4 в режиме визуализации тестирования.

Есть какое-нибудь глобальное решение данной проблемы, чтобы не устраивать пляски с бубном по каждому контролу?

Vasiliy Pushkaryov
Vasiliy Pushkaryov | 25 мар. 2023 в 12:43
Vitaliy Davydov #:

Добрый день!

Спасибо, разобрался.

Теперь другие проблемы - все эти панели из стандартной библиотеки,

абсолютно нефункциональны в тестере МТ4 в режиме визуализации тестирования.

Есть какое-нибудь глобальное решение данной проблемы, чтобы не устраивать пляски с бубном по каждому контролу?

Что значит не функциональны? У меня в тестере МТ4 они свои функции выполняют.

Приводил пример, когда унаследовался от CButton и добавил туда функцию cMyButton::isButtonPushed() для работы с тестером.

Тоже самое я себе сделал для CEdit, CCheckBox и использую потом всегда своих наследников. Это разовая работа.

В файле TestPush был образец функции checkPushingButttons() :

void OnTick()
{
    if(MQLInfoInteger(MQL_VISUAL_MODE)) ExtDialog.checkPushingButttons();
}

//+------------------------------------------------------------------------------------------------------------------+
//| Для тестера и OnTick. Проверим нажатие кнопки (или объекта)
//+------------------------------------------------------------------------------------------------------------------+
void CAppWindowCorrectMinimization::checkPushingButttons()
{
   if(m_button1.isButtonPushed())    print1();
   if(m_button2.isButtonPushed())    print2();
}


Вот из моего работающего кода:

//+------------------------------------------------------------------------------------------------------------------+
//| Для тестера и OnTick. Проверим нажатие кнопки (или объекта)
//+------------------------------------------------------------------------------------------------------------------+
void cInterface::checkPushingButttons()
{
  if(btnOnOff.isButtonPushed())    OnClickOnOff();
  if(btnLong.isButtonPushed())     OnClickLong();
  if(btnShort.isButtonPushed())    OnClickShort();

  if(btnCloseOff.isButtonPushed()) OnClickCloseOff();
  if(btnProtocol.isButtonPushed()) OnClickProtocol();
  if(btnOpposMA6.isButtonPushed()) OnClickOpposMA6();

  if(editMA2plus.isEditChanged())  OnEditMA2plus();
  if(editPunct.isEditChanged())    OnEditPuncture();

Т.е. в конце концов разница с работой без тестера и в тестере сводится к добавлению одной функции checkPushingButttons(), где на каждый контрол добавим строку, и проверим через тик нажатие или ввод.

Подготовьте шаблоны, пару раз сделаете, думаю, потом привыкнете.

Anton Iaroshenko
Anton Iaroshenko | 10 февр. 2024 в 12:49

класс CAppDialog глючный. 

После хотя бы однократного использования для ВСЕХ новых графиков свойство CHART_EVENT_MOUSE_MOVE становится равным false.

Как вернуть true по умолчанию??

Работаем с результатами оптимизации через графический интерфейс Работаем с результатами оптимизации через графический интерфейс
Продолжаем развивать тему обработки и анализа результатов оптимизации. На этот раз задача состоит в том, чтобы выбрать 100 лучших результатов оптимизации и отобразить их в таблице графического интерфейса. Сделаем так, чтобы пользователь, выделяя ряд в таблице результатов оптимизации, получал мультисимвольный график баланса и просадки на отдельных графиках.
Создание многомодульных советников Создание многомодульных советников
Язык программирования MQL позволяет реализовать концепцию модульного проектирования торговых стратегий. В статье показан пример создания многомодульного советника, состоящего из отдельно скомпилированных файловых модулей.
Строим индикатор ZigZag по осцилляторам. Пример выполнения технического задания Строим индикатор ZigZag по осцилляторам. Пример выполнения технического задания
В статье демонстрируется создание индикатора ZigZag в соответствии с одним из примеров заданий, описанным в статье "Как составить техническое задание при заказе индикатора". Индикатор строится по экстремумам, которые определяются с помощью осциллятора. В индикаторе предусмотрена возможность использования одного из пяти осцилляторов на выбор: WPR, CCI, Chaikin, RSI, Stochastic Oscillator.
Создание пользовательской новостной ленты в MetaTrader 5 Создание пользовательской новостной ленты в MetaTrader 5
В статье рассматривается возможность создания гибкой новостной ленты, предоставляющей множество опций по выбору типа новостей и их источника. Статья показывает, как можно интегрировать веб-API с терминалом MetaTrader 5.