English 中文 Español Deutsch 日本語 Português
preview
Сделайте торговые графики лучше с интерактивным графическим интерфейсом на основе MQL5 (Часть III): Простой перемещаемый торговый интерфейс

Сделайте торговые графики лучше с интерактивным графическим интерфейсом на основе MQL5 (Часть III): Простой перемещаемый торговый интерфейс

MetaTrader 5Трейдинг | 30 ноября 2023, 13:01
1 078 14
Kailash Bai Mina
Kailash Bai Mina

Введение

Для начала давайте вспомним, что мы рассмотрели в предыдущих двух частях:

1. В первой части мы рассмотрели концепцию событий графика, а затем создали две простые перемещаемые панели на одном графике.

2. Во второй части мы пошли еще дальше. Мы использовали классы в файле .mqh, чтобы сделать наш код более эффективным и универсальным, готовым к интеграции с полномасштабными советниками/индикаторами.

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

Вот краткий обзор того, что мы рассмотрим в этой статье:

  1. Что мы создаем?
  2. Создание простой статической торговой панели
  3. Обсуждение перемещения нашей статической панели со всеми элементами внутри нее
  4. Применение выбранного подхода на практике для создания перемещаемой панели
  5. Заключение


Что мы создаем?

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

Во-первых, нам нужно создать эту статическую панель управления, то есть советник Simple Trading. Крайне важно сделать это эффективно, поскольку мы создаем полноценный советник. Под эффективностью я подразумеваю, что мы не можем просто открыть файл и написать туда весь код. Вместо этого нам нужен хорошо продуманный план, который позволит нам написать минимальный код в нескольких хорошо организованных .mqh-файлах. Самое главное, мы должны избегать многократного дублирования одного и того же кода для создания необходимых статических графических интерфейсов для нашей перемещаемой панели.

Вот базовая статическая панель, которую мы возьмем за основу:

Рис 1. Простая статическая панель

Рис 1. Простая статическая панель


Она включает в себя:

Элемент Описание
Label 1 Текст заголовка (Simple Trading EA V1.0)
Label 2 Размер лота.
Edit 1 Поле редактирования белого цвета с написанным внутри него значением 0,01.
Button 1 Зеленая кнопка Buy.
Button 2 Красная кнопка Sell.
Rectangle Label 1 Строка заголовка, темно-синяя полоса с надписью "Simple Trading EA V1.0".
Rectangle Label 2  Основная область панели голубого цвета.

Итак, наша панель состоит из этих семи компонентов. На мой взгляд, это довольно красивая панель инструментов, которую мы создали, просто объединив эти семь элементов.

Теперь займемся кодом.


Создание простой статической торговой панели

Какие классы нам понадобятся?

Нам понадобятся 2 метки (Label), 2 кнопки (Button), 1 поле редактирования (Edit) и 2 прямоугольные метки (Rectangle Label). Итак, давайте создадим 4 файла .mqh, по одному на каждый из них. Вот структура папок нашего проекта:

  • Simple Trading EA/
    • SimpleTradingEA.mq5
    • Button.mqh
    • Label.mqh
    • Edit.mqh
    • RectangleLabel.mqh

Это файлы, в которых мы будем писать наш код. Теперь давайте создадим наш первый файл SimpleTradingEA.mq5, который является основным файлом советника.

Я удалил функцию OnTick(), так как она нам в этом проекте не понадобится. Вот как файл выглядит на данный момент:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
   {
    return(INIT_SUCCEEDED);
   }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
   {
   }
//+------------------------------------------------------------------+

Давайте теперь составим план. Мы построим нашу статическую панель в следующем порядке:

  1. Строка заголовка
  2. Основная часть панели
  3. Текст заголовка
  4. Текст "Lot Size:" (размер лота)
  5. Поле редактирования
  6. Кнопки Buy и Sell
  7. Необходимые завершающие штрихи

Всё выглядит логично. Давайте начнем.

  1. Строка заголовка

    Чтобы создать строку заголовка, нам нужно использовать объект Rectangle Label. Итак, давайте создадим класс, который будет обрабатывать все, что связано с объектом Rectangle Label. Создадим .mqh-файл. Для простоты назовем его RectangleLabel.mqh, а класс будет носить название RectangleLabel.
    Вот пустой класс, который мы создали:

    //+------------------------------------------------------------------+
    //| Class Definition: RectangleLabel                                 |
    //+------------------------------------------------------------------+
    class RectangleLabel
       {
    public:
                         RectangleLabel(void);
                        ~RectangleLabel(void);
       };
    
    //+------------------------------------------------------------------+
    //| Constructor: RectangleLabel                                      |
    //+------------------------------------------------------------------+
    RectangleLabel::RectangleLabel(void)
       {
       }
    
    //+------------------------------------------------------------------+
    //| Destructor: RectangleLabel                                       |
    //+------------------------------------------------------------------+
    RectangleLabel::~RectangleLabel(void)
       {
       }
    //+------------------------------------------------------------------+

    Нам понадобятся некоторые функции:

    1. Create             -> Создание прямоугольной метки
    2. Destroy            -> Удаление панели
    3. SetBorderType  -> Тип границы
    4. SetBGColor       -> Цвет фона

    Объявим эти функции в списке функций-членов. Теперь наш класс выглядит так:

    //+------------------------------------------------------------------+
    //| Class Definition: RectangleLabel                                 |
    //+------------------------------------------------------------------+
    class RectangleLabel
       {
    public:
                         RectangleLabel(void); // Constructor
                        ~RectangleLabel(void); // Destructor
        void             Create(string name, int xDis, int yDis, int xSize, int ySize); //Creates a Rectangle Label with the given parameters
        void             Destroy(); // Destroys the Rectangle Label
        void             SetBorderType(ENUM_BORDER_TYPE borderType); // Sets the border type of the Rectangle Label
        void             SetBGColor(color col); // Sets the background color of the Rectangle Label
       };
    //+------------------------------------------------------------------+

    Давайте запишем базовую функцию создания:

    //+------------------------------------------------------------------+
    //| RectangleLabel Class - Create Method                             |
    //+------------------------------------------------------------------+
    void RectangleLabel::Create(string name, int xDis, int yDis, int xSize, int ySize)
       {
        ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0); // Create the Rectangle Label object
        ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xDis); // Set the X-axis distance
        ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yDis); // Set the Y-axis distance
        ObjectSetInteger(0, name, OBJPROP_XSIZE, xSize); // Set the X size
        ObjectSetInteger(0, name, OBJPROP_YSIZE, ySize); // Set the Y size
       }
    //+------------------------------------------------------------------+

    Создадим Destroy, SetBorderType и SetBGColor в одной строке. Вот наш обновленный класс:

    //+------------------------------------------------------------------+
    //| Class Definition for the Rectangle Label                         |
    //+------------------------------------------------------------------+
    class RectangleLabel
       {
    private:
        string           _name; // Name of the rectangle label
    public:
                         RectangleLabel(void); // Constructor
                        ~RectangleLabel(void); // Destructor
    
        void             Create(string name, int xDis, int yDis, int xSize, int ySize); // Method to create a rectangle label with given dimensions
    
        void             Destroy() {ObjectDelete(0, _name);} // Method to delete the object using the object's name
    
        void             SetBorderType(ENUM_BORDER_TYPE borderType) {ObjectSetInteger(0, _name, OBJPROP_BORDER_TYPE, borderType);} // Method to set the border type for the rectangle label
    
        void             SetBGColor(color col) {ObjectSetInteger(0, _name, OBJPROP_BGCOLOR, col);} // Method to set the background color for the rectangle label
       };
    //+------------------------------------------------------------------+

    Также мы добавили частную переменную "_name", поскольку ObjectDelete требует имя, и мы установили "_name" в функции Create:

    //+------------------------------------------------------------------+
    //| Rectangle Label Creation Method                                  |
    //+------------------------------------------------------------------+
    void RectangleLabel::Create(string name, int xDis, int yDis, int xSize, int ySize)
       {
        ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0); // Create rectangle label object
        ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xDis); // Set X distance
        ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yDis); // Set Y distance
        ObjectSetInteger(0, name, OBJPROP_XSIZE, xSize); // Set X size
        ObjectSetInteger(0, name, OBJPROP_YSIZE, ySize); // Set Y size
        _name = name; // Assign the name to the member variable
       }
    //+------------------------------------------------------------------+

    Мы просто добавили "_name = name;" в последней строке для присвоения переменной _name в качестве имени созданной прямоугольной метки.

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

    Воспользуемся этим классом в основном файле, например SimpleTradingEA.mq5, чтобы увидеть результат:


    Сначала мы включили файл RectangleLabel.mqh с помощью #include и создали экземпляр класса с именем TitleBar, поскольку мы создаем строку заголовка панели с помощью экземпляра класса RectangleLabel. Мы будем использовать его снова для основного тела панели.

    Затем мы использовали этот экземпляр для создания прямоугольной метки на диаграмме по координатам (100,100) и размером 200x20. Затем мы делаем его границу плоской (BORDER_FLAT), так как, по моему мнению, это выглядит лучше. Вы можете изменить эту настройку по своему усмотрению. Затем мы используем функцию ChartRedraw(0) для перерисовки графика. Таким образом панель будет создана на графике немедленно. В противном случае нужно будет дождаться следующего обновления цены, то есть тика.

    Всё было реализовано в OnInit(). Необходимо только одно выполнение для создания и отображения панели на графике.

    Наконец, мы удаляем панель с помощью созданной нами функции Destroy() в OnDeinit(), то есть при удалении советника с графика.

    Результат:

    Рис 2. Строка заголовка

    Рис 2. Строка заголовка


  2. Основная часть панели

    Снова воспользуемся классом RectangleLabel для создания основного тела. Нам просто нужно создать еще один экземпляр. Давайте назовем его "MainDashboardBody" и добавим приведенный ниже простой код в OnInit() после создания строки заголовка, а затем, наконец, добавим MainDashboardBody.Destroy() в OnDeinit():

    // Creating a rectangle label called "MainDashboardBody" with specific dimensions
    MainDashboardBody.Create("MainDashboardBody", 100, 119, 200, 100);
    // Setting the border type of the "MainDashboardBody" rectangle label to be flat
    MainDashboardBody.SetBorderType(BORDER_FLAT);
    После этого наш код выглядит так:
    #include "RectangleLabel.mqh" // Including the RectangleLabel class definition
    RectangleLabel TitleBar; // Declaration of a TitleBar object
    RectangleLabel MainDashboardBody; // Declaration of a MainDashboardBody object
    
    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
       {
        TitleBar.Create("TitleBar", 100, 100, 200, 20); // Creating the TitleBar with specified dimensions
        TitleBar.SetBorderType(BORDER_FLAT); // Setting the border type of TitleBar to be flat
    
        MainDashboardBody.Create("MainDashboardBody", 100, 119, 200, 100); // Creating the MainDashboardBody with specified dimensions
        MainDashboardBody.SetBorderType(BORDER_FLAT); // Setting the border type of MainDashboardBody to be flat
    
        ChartRedraw(0); // Redrawing the chart to reflect changes
        return(INIT_SUCCEEDED); // Indicating successful initialization
       }
    
    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
       {
        MainDashboardBody.Destroy(); // Destroying the MainDashboardBody object
        TitleBar.Destroy(); // Destroying the TitleBar object
       }
    //+------------------------------------------------------------------+

    Результат выглядит неплохо:

    Рис. 3. Добавлено основное тело панели

    Рис. 3. Добавлено основное тело панели



  3. Текст заголовка

    Чтобы добавить текст заголовка, нам нужно создать класс, аналогичный RectangleLabel, но специально для меток, что позволит нам добавлять текст. Вот код нового класса с именем Label:

    //+------------------------------------------------------------------+
    //| Label class definition                                           |
    //+------------------------------------------------------------------+
    class Label
       {
    private:
        string           _name; // Name of the label
    public:
                         Label(void); // Constructor
                        ~Label(void); // Destructor
                        
        void             Create(string name, int xDis, int yDis); // Method to create a label    
        void             Destroy() {ObjectDelete(0, _name);} // Method to destroy a label    
        void             SetTextColor(color col) {ObjectSetInteger(0, _name, OBJPROP_COLOR, col);} // Method to set the text color    
        void             SetText(string text) {ObjectSetString(0, _name, OBJPROP_TEXT, text);} // Method to set the text content    
        string           GetText() {return ObjectGetString(0, _name, OBJPROP_TEXT);} // Method to retrieve the text content    
        void             SetFontSize(int fontSize) {ObjectSetInteger(0, _name, OBJPROP_FONTSIZE, fontSize);} // Method to set the font size    
        void             SetFont(string fontName) {ObjectSetString(0, _name, OBJPROP_FONT, fontName);} // Method to set the font name
       };
    
    //+------------------------------------------------------------------+
    //| Constructor                                                      |
    //+------------------------------------------------------------------+
    Label::Label(void)
       {
    
       }
    
    //+------------------------------------------------------------------+
    //| Destructor                                                       |
    //+------------------------------------------------------------------+
    Label::~Label(void)
       {
    
       }
    
    //+------------------------------------------------------------------+
    //| Method to create a label object                                  |
    //+------------------------------------------------------------------+
    void Label::Create(string name, int xDis, int yDis)
       {
        // Code to create label object, set its position, and assign its name
        ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
        ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xDis);
        ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yDis);
        _name = name;
       }
    //+------------------------------------------------------------------+
    • Создан класс с именем Label в новом .mqh-файле с именем Label.mqh
    • Объявлена частная переменная _name для непубличного хранения имени
    • Создана функцию Create с тремя обязательными параметрами: name, xDis и yDis Размер не имеет значения для объекта Label. Чтобы изменить размер текста, мы изменяем размер шрифта
    • Создана функция Destroy для удаления метки
    • Создана функция SetTextColor для установки цвета текста
    • Создана функция для установки текста объекта метки
    • Создана функция GetText для получения текста объекта Label, который возвращает строку
    • Создана функцию SetFontSize для установки размера шрифта
    • Создана функция для установки шрифта. Требуется имя шрифта в строке. Конечно, шрифт должен быть установлен в операционной системе.

    Теперь давайте воспользуемся им для создания двух объектов меток на графике.
    Теперь SimpleTradingEA.mq5 выглядит так:
    #include "RectangleLabel.mqh" // Including the RectangleLabel class definition
    RectangleLabel TitleBar; // Declaration of a TitleBar object
    RectangleLabel MainDashboardBody; // Declaration of a MainDashboardBody object
    
    #include "Label.mqh" // Including the Label class definition
    Label TitleText; // Declaration of a Label object
    
    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
       {
        TitleBar.Create("TitleBar", 100, 100, 200, 20); // Creating the TitleBar with specified dimensions
        TitleBar.SetBorderType(BORDER_FLAT); // Setting the border type of TitleBar to be flat
    
        MainDashboardBody.Create("MainDashboardBody", 100, 119, 200, 100); // Creating the MainDashboardBody with specified dimensions
        MainDashboardBody.SetBorderType(BORDER_FLAT); // Setting the border type of MainDashboardBody to be flat
        
        TitleText.Create("TitleText", 110, 101); // Creating the TitleText at (110,101)
        TitleText.SetText("Simple Trading EA V1.0"); // Setting its text to "Simple Trading EA V1.0"
        TitleText.SetFontSize(10); // Setting its font size to 10
        TitleText.SetTextColor(clrBlack); // Setting its text color to clrBlack
        
        ChartRedraw(0); // Redrawing the chart to reflect changes
        return(INIT_SUCCEEDED); // Indicating successful initialization
       }
    
    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
       {
        MainDashboardBody.Destroy(); // Destroying the MainDashboardBody object
        TitleBar.Destroy(); // Destroying the TitleBar object
        TitleText.Destroy(); // Destroying the TitleText object
       }
    //+------------------------------------------------------------------+
    • Создан экземпляр метки с именем TitleText
    • Использована функцию TitleText.Create для создания TitleText
    • Использован TitleText.SetText, чтобы установить для TitleText значение "Simple Trading EA V1.0"
    • Использован TitleText.SetFontSize для установки FontSize равным 10
    • Использован TitleText.SetTextColor для установки черного цвета
    • Использован TitleText.Destroy для уничтожения объекта TitleText в OnDeinit

    Результат:


    Рис 4. Добавлен текст заголовка
    Рис 4. Добавлен текст заголовка

  4. Текст "Lot Size:" (размер лота)

    Для текста "Lot Size:" выполните процесс, аналогичный тексту заголовка. Окончательный код выглядит так:

    #include "RectangleLabel.mqh" // Including the RectangleLabel class definition
    RectangleLabel TitleBar; // Declaration of a TitleBar object
    RectangleLabel MainDashboardBody; // Declaration of a MainDashboardBody object
    
    #include "Label.mqh" // Including the Label class definition
    Label TitleText; // Declaration of a Label object
    Label LotSizeText; // Declaration of a LotSizeText object
    
    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
       {
        TitleBar.Create("TitleBar", 100, 100, 200, 20); // Creating the TitleBar with specified dimensions
        TitleBar.SetBorderType(BORDER_FLAT); // Setting the border type of TitleBar to be flat
    
        MainDashboardBody.Create("MainDashboardBody", 100, 119, 200, 100); // Creating the MainDashboardBody with specified dimensions
        MainDashboardBody.SetBorderType(BORDER_FLAT); // Setting the border type of MainDashboardBody to be flat
        
        TitleText.Create("TitleText", 110, 101); // Creating the TitleText at (110,101)
        TitleText.SetText("Simple Trading EA V1.0"); // Setting its text to "Simple Trading EA V1.0"
        TitleText.SetFontSize(10); // Setting its font size to 10
        TitleText.SetTextColor(clrBlack); // Setting its text color to clrBlack
        
        LotSizeText.Create("LotSizeText", 110, 140); // Creating the LotSizeText at (110,140)
        LotSizeText.SetText("Lot Size:"); // Setting its text to "Lot Size:"
        LotSizeText.SetFontSize(12); // Setting its font size to 12
        LotSizeText.SetTextColor(clrBlack); // Setting its text color to clrBlack
        
        ChartRedraw(0); // Redrawing the chart to reflect changes
        return(INIT_SUCCEEDED); // Indicating successful initialization
       }
    
    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
       {
        MainDashboardBody.Destroy(); // Destroying the MainDashboardBody object
        TitleBar.Destroy(); // Destroying the TitleBar object
        TitleText.Destroy(); // Destroying the TitleText object
        LotSizeText.Destroy(); // Destroying the LotSizeText object
       }
    //+------------------------------------------------------------------+
    • Создан экземпляр Label с именем LotSizeText
    • Использована функция LotSizeText.Create для создания текста
    • Использован LotSizeText.SetText для установки текста "Lot Size:"
    • Использован LotSizeText.SetFontSize, чтобы установить FontSize равным 12.
    • Использован LotSizeText.SetTextColor, чтобы установить черный цвет.
    • Использован LotSizeText.Destroy для уничтожения объекта Label в OnDeinit.

    На этом всё. Результат:


    Рис. 5. Добавлен текст размера лота
    Рис. 5. Добавлен текст "Lot Size:"





  5. Поле редактирования

    Для поля редактирования мы создадим класс, очень похожий на класс Label. Код нового класса Edit:

    //+------------------------------------------------------------------+
    //| Edit class definition                                            |
    //+------------------------------------------------------------------+
    class Edit
       {
    private:
        string           _name; // Name of the edit control
    public:
                         Edit(void); // Constructor
                        ~Edit(void); // Destructor
                        
        void             Create(string name, int xDis, int yDis, int xSize, int ySize); // Method to create an edit control    
        void             Destroy() {ObjectDelete(0, _name);} // Method to destroy an edit control    
        void             SetBorderColor(color col) {ObjectSetInteger(0, _name, OBJPROP_BORDER_COLOR, col);} // Method to set the border color    
        void             SetBGColor(color col) {ObjectSetInteger(0, _name, OBJPROP_BGCOLOR, col);} // Method to set the background color    
        void             SetTextColor(color col) {ObjectSetInteger(0, _name, OBJPROP_COLOR, col);} // Method to set the text color    
        void             SetText(string text) {ObjectSetString(0, _name, OBJPROP_TEXT, text);} // Method to set the text content    
        string           GetText() {return ObjectGetString(0, _name, OBJPROP_TEXT);} // Method to retrieve the text content
       };
    
    //+------------------------------------------------------------------+
    //| Constructor                                                      |
    //+------------------------------------------------------------------+
    Edit::Edit(void)
       {
    
       }
    
    //+------------------------------------------------------------------+
    //| Destructor                                                       |
    //+------------------------------------------------------------------+
    Edit::~Edit(void)
       {
    
       }
    
    //+------------------------------------------------------------------+
    //| Method to create an edit control object                          |
    //+------------------------------------------------------------------+
    void Edit::Create(string name, int xDis, int yDis, int xSize, int ySize)
       {
        // Code to create edit control object, set its position, size, and assign its name
        ObjectCreate(0, name, OBJ_EDIT, 0, 0, 0);
        ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xDis);
        ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yDis);
        ObjectSetInteger(0, name, OBJPROP_XSIZE, xSize);
        ObjectSetInteger(0, name, OBJPROP_YSIZE, ySize);
        _name = name;
       }
    //+------------------------------------------------------------------+
    • Создан класс Edit в новом .mqh-файле с именем Edit.mqh
    • Объявлена частная переменная _name для непубличного хранения имени
    • Создана функция Create с пятью обязательными параметрами: name, xDis, yDis, xSize и ySize
    • Создана функция Destroy для удаления объекта редактирования
    • Создана функция SetBorderColor для установки цвета границы
    • Создана функция SetBGColor для установки цвета фона WhiteSmoke
    • Создана функция SetTextColor для установки цвета текста внутри поля редактирования
    • Создана функция SetText для установки текста
    • Создана функция GetText для получения текста

    Теперь вы можете использовать класс Edit в SimpleTradingEA, как показано ниже:

    #include "RectangleLabel.mqh" // Including the RectangleLabel class definition
    RectangleLabel TitleBar; // Declaration of a TitleBar object
    RectangleLabel MainDashboardBody; // Declaration of a MainDashboardBody object
    
    #include "Label.mqh" // Including the Label class definition
    Label TitleText; // Declaration of a Label object
    Label LotSizeText; // Declaration of a LotSizeText object
    
    #include "Edit.mqh" // Including the Edit class definition
    Edit LotSize; // Declaration of a LotSize object
    
    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
       {
        TitleBar.Create("TitleBar", 100, 100, 200, 20); // Creating the TitleBar with specified dimensions
        TitleBar.SetBorderType(BORDER_FLAT); // Setting the border type of TitleBar to be flat
    
        MainDashboardBody.Create("MainDashboardBody", 100, 119, 200, 100); // Creating the MainDashboardBody with specified dimensions
        MainDashboardBody.SetBorderType(BORDER_FLAT); // Setting the border type of MainDashboardBody to be flat
        
        TitleText.Create("TitleText", 110, 101); // Creating the TitleText at (110,101)
        TitleText.SetText("Simple Trading EA V1.0"); // Setting its text to "Simple Trading EA V1.0"
        TitleText.SetFontSize(10); // Setting its font size to 10
        TitleText.SetTextColor(clrBlack); // Setting its text color to clrBlack
        
        LotSizeText.Create("LotSizeText", 110, 140); // Creating the LotSizeText at (110,140)
        LotSizeText.SetText("Lot Size:"); // Setting its text to "Lot Size:"
        LotSizeText.SetFontSize(12); // Setting its font size to 12
        LotSizeText.SetTextColor(clrBlack); // Setting its text color to clrBlack
        
        LotSize.Create("LotSize", 220, 140, 50, 20); // Creating the LotSize with specified dimensions
        LotSize.SetBorderColor(clrBlack); // Setting its Border Color to clrBlack
        LotSize.SetBGColor(clrWhiteSmoke); // Setting its BG Color to clrWhiteSmoke
        LotSize.SetText("0.01"); // Setting its text to 0.01
        LotSize.SetTextColor(clrBlack); // Setting its text color to clrBlack
        
        ChartRedraw(0); // Redrawing the chart to reflect changes
        return(INIT_SUCCEEDED); // Indicating successful initialization
       }
    
    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
       {
        MainDashboardBody.Destroy(); // Destroying the MainDashboardBody object
        TitleBar.Destroy(); // Destroying the TitleBar object
        TitleText.Destroy(); // Destroying the TitleText object
        LotSizeText.Destroy(); // Destroying the LotSizeText object
        LotSize.Destroy(); // Destroying the LotSize object
       }
    //+------------------------------------------------------------------+
    • Создан экземпляр Edit с именем LotSize
    • Использована функция LotSize.Create для создания объекта редактирования
    • Использован LotSize.SetBorderColor, чтобы установить черный цвет границы
    • Использован LotSize.SetBGColor для установки цвета фона WhiteSmoke
    • Использован LotSize.SetText для установки текста 0,01, обозначающего размер лота
    • Использован LotSize.SetTextColor, чтобы установить черный цвет текста внутри поля редактирования
    • Использован LotSize.Destroy для удаления объекта Edit в OnDeinit.

  6. Кнопки Buy и Sell

    Наконец-то мы добрались до кнопок. Создадим класс для кнопок аналогично тому, как мы это делали для других элементов:

    //+------------------------------------------------------------------+
    //| Button class definition                                          |
    //+------------------------------------------------------------------+
    class Button
       {
    private:
        string           _name; // Name of the button control
    
    public:
                         Button(void); // Constructor
                        ~Button(void); // Destructor
                        
        void             Create(string name, int xDis, int yDis, int xSize, int ySize); // Method to create a button control    
        void             SetBorderColor(color col) {ObjectSetInteger(0, _name, OBJPROP_BORDER_COLOR, col);} // Method to set the border color    
        void             SetBGColor(color col) {ObjectSetInteger(0, _name, OBJPROP_BGCOLOR, col);} // Method to set the background color    
        void             SetText(string text) {ObjectSetString(0, _name, OBJPROP_TEXT, text);} // Method to set the text content    
        void             Destroy() {ObjectDelete(0, _name);} // Method to destroy a button control
       };
    
    //+------------------------------------------------------------------+
    //| Constructor                                                      |
    //+------------------------------------------------------------------+
    Button::Button(void)
       {
    
       }
    
    //+------------------------------------------------------------------+
    //| Destructor                                                       |
    //+------------------------------------------------------------------+
    Button::~Button(void)
       {
    
       }
    
    //+------------------------------------------------------------------+
    //| Method to create a button control object                         |
    //+------------------------------------------------------------------+
    void Button::Create(string name, int xDis = 0, int yDis = 0, int xSize = 0, int ySize = 0)
       {
        // Code to create button control object, set its position, size, and assign its name
        ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0);
        ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xDis);
        ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yDis);
        ObjectSetInteger(0, name, OBJPROP_XSIZE, xSize);
        ObjectSetInteger(0, name, OBJPROP_YSIZE, ySize);
        _name = name;
       }
    //+------------------------------------------------------------------+
    

    В новом файле .mqh с именем Button.mqh мы создали класс с именем Button. Мы объявили частную переменную _name для непубличного хранения имени. Мы также создали следующие функции:

      • Функция Create с пятью обязательными параметрами: name, xDis, yDis, xSize и ySize.
      • Функция Destroy для удаления объекта кнопки (Button Object).
      • Функция SetBorderColor для установки цвета границы (Border Color).
      • Функция SetBGColor для установки цвета фона WhiteSmoke.
      • Функция SetText для установки текста.

      Теперь посмотрим на основной файл SimpleTradingEA.mq5 после добавления кнопок. Вы заметите, что теперь он содержит экземпляры RectangleLabel, Label, Edit, Button для BuyButton и SellButton.

      #include "RectangleLabel.mqh" // Including the RectangleLabel class definition
      RectangleLabel TitleBar; // Declaration of a TitleBar object
      RectangleLabel MainDashboardBody; // Declaration of a MainDashboardBody object
      
      #include "Label.mqh" // Including the Label class definition
      Label TitleText; // Declaration of a Label object
      Label LotSizeText; // Declaration of a LotSizeText object
      
      #include "Edit.mqh" // Including the Edit class definition
      Edit LotSize; // Declaration of a LotSize object
      
      #include "Button.mqh" // Including the Button class definition
      Button BuyButton; // Declaration of a BuyButton object
      Button SellButton; // Declaration of a SellButton object
      
      //+------------------------------------------------------------------+
      //| Expert initialization function                                   |
      //+------------------------------------------------------------------+
      int OnInit()
         {
          TitleBar.Create("TitleBar", 100, 100, 200, 20); // Creating the TitleBar with specified dimensions
          TitleBar.SetBorderType(BORDER_FLAT); // Setting the border type of TitleBar to be flat
      
          MainDashboardBody.Create("MainDashboardBody", 100, 119, 200, 100); // Creating the MainDashboardBody with specified dimensions
          MainDashboardBody.SetBorderType(BORDER_FLAT); // Setting the border type of MainDashboardBody to be flat
          
          TitleText.Create("TitleText", 110, 101); // Creating the TitleText at (110,101)
          TitleText.SetText("Simple Trading EA V1.0"); // Setting its text to "Simple Trading EA V1.0"
          TitleText.SetFontSize(10); // Setting its font size to 10
          TitleText.SetTextColor(clrBlack); // Setting its text color to clrBlack
          
          LotSizeText.Create("LotSizeText", 110, 140); // Creating the LotSizeText at (110,140)
          LotSizeText.SetText("Lot Size:"); // Setting its text to "Lot Size:"
          LotSizeText.SetFontSize(12); // Setting its font size to 12
          LotSizeText.SetTextColor(clrBlack); // Setting its text color to clrBlack
          
          LotSize.Create("LotSize", 220, 140, 50, 20); // Creating the LotSize with specified dimensions
          LotSize.SetBorderColor(clrBlack); // Setting its Border Color to clrBlack
          LotSize.SetBGColor(clrWhiteSmoke); // Setting its BG Color to clrWhiteSmoke
          LotSize.SetText("0.01"); // Setting its text to 0.01
          LotSize.SetTextColor(clrBlack); // Setting its text color to clrBlack
          
          BuyButton.Create("BuyButton", 110, 180, 80, 25); // Creating the BuyButton with specified dimensions
          BuyButton.SetBorderColor(clrBlack); // Setting its Border Color to clrBlack
          BuyButton.SetText("Buy"); // Setting its text to "Buy"
          BuyButton.SetBGColor(clrLime); // Setting its BG Color to clrLime
          
          SellButton.Create("SellButton", 210, 180, 80, 25); // Creating the SellButton with specified dimensions
          SellButton.SetBorderColor(clrBlack); // Setting its Border Color to clrBlack
          SellButton.SetText("Sell"); // Setting its text to "Sell"
          SellButton.SetBGColor(clrRed); // Setting its BG Color to clrRed
          
          ChartRedraw(0); // Redrawing the chart to reflect changes
          return(INIT_SUCCEEDED); // Indicating successful initialization
         }
      
      //+------------------------------------------------------------------+
      //| Expert deinitialization function                                 |
      //+------------------------------------------------------------------+
      void OnDeinit(const int reason)
         {
          MainDashboardBody.Destroy(); // Destroying the MainDashboardBody object
          TitleBar.Destroy(); // Destroying the TitleBar object
          TitleText.Destroy(); // Destroying the TitleText object
          LotSizeText.Destroy(); // Destroying the LotSizeText object
          LotSize.Destroy(); // Destroying the LotSize object
          BuyButton.Destroy(); // Destroying the BuyButton object
          SellButton.Destroy(); // Destroying the SellButton object
         }
      //+------------------------------------------------------------------+
      • Создан экземпляр Button с именем BuyButton
      • Использована функция BuyButton.Create для создания объекта редактирования (Edit Object)
      • Использован BuyButton.SetBorderColor для установки черного цвета границы
      • Использован BuyButton.SetBGColor для установки цвета фона Lime.
      • Использован BuyButton.SetText для установки текста Buy
      • Использован BuyButton.Destroy для удаления объекта Button в OnDeinit

      Для кнопки Sell:

      • Создан экземпляр Button с именем SellButton
      • Использована функция SellButton.Create для создания объекта кнопки (Button Object)
      • Использован SellButton.SetBorderColor для установки черного цвета границы
      • Использован SellButton.SetBGColor для установки красного цвета фона
      • Использован SellButton.SetText для установки текста Sell
      • Использован SellButton.Destroy для удаления объекта Button в OnDeinit

      Результат:


      Рис. 6. Добавлены кнопки Buy и Sell
      Рис. 6. Добавлены кнопки Buy и Sell

    • Последние штрихи

    • Займемся цветами. Внесем следующие изменения:


      • Изменим цвет строки заголовка на темно-синий
      • Изменим цвет тела основной панели на светло-голубой
      • Изменим цвет текста заголовка с черного на белый
      • Изменим цвет текста Lot Size с черного на белый
      • Добавить функцию покупки/продажи

      Окончательный код SimpleTradingEA.mq5 включает изменения цвета и торговую библиотеку. Он также создает функцию OnChartEvent, чтобы при нажатии кнопки Buy или Sell соответствующий ордер размещался немедленно.

      #include "RectangleLabel.mqh" // Including the RectangleLabel class definition
      RectangleLabel TitleBar; // Declaration of a TitleBar object
      RectangleLabel MainDashboardBody; // Declaration of a MainDashboardBody object
      
      #include "Label.mqh" // Including the Label class definition
      Label TitleText; // Declaration of a Label object
      Label LotSizeText; // Declaration of a LotSizeText object
      
      #include "Edit.mqh" // Including the Edit class definition
      Edit LotSize; // Declaration of a LotSize object
      
      #include "Button.mqh" // Including the Button class definition
      Button BuyButton; // Declaration of a BuyButton object
      Button SellButton; // Declaration of a SellButton object
      
      //+------------------------------------------------------------------+
      //| Expert initialization function                                   |
      //+------------------------------------------------------------------+
      int OnInit()
         {
          TitleBar.Create("TitleBar", 100, 100, 200, 20); // Creating the TitleBar with specified dimensions
          TitleBar.SetBorderType(BORDER_FLAT); // Setting the border type of TitleBar to be flat
          TitleBar.SetBGColor(C'27, 59, 146'); // Setting the color to RGB code: C'27, 59, 146'
      
          MainDashboardBody.Create("MainDashboardBody", 100, 119, 200, 100); // Creating the MainDashboardBody with specified dimensions
          MainDashboardBody.SetBorderType(BORDER_FLAT); // Setting the border type of MainDashboardBody to be flat
          MainDashboardBody.SetBGColor(C'102, 152, 250'); // Setting the color to RGB code: C'102, 152, 250'
          
          TitleText.Create("TitleText", 110, 101); // Creating the TitleBar at (110,101)
          TitleText.SetText("Simple Trading EA V1.0"); // Setting its text to "Simple Trading EA V1.0"
          TitleText.SetFontSize(10); // Setting its font size to 10
          TitleText.SetTextColor(clrWhite); // Setting its text color to clrWhite
          
          LotSizeText.Create("LotSizeText", 110, 140); // Creating the LotSizeText at (110,140)
          LotSizeText.SetText("Lot Size:"); // Setting its text to "Lot Size:"
          LotSizeText.SetFontSize(12); // Setting its font size to 12
          LotSizeText.SetTextColor(clrWhite); // Setting its text color to clrWhite
          
          LotSize.Create("LotSize", 220, 140, 50, 20); // Creating the LotSize with specified dimensions
          LotSize.SetBorderColor(clrBlack); // Setting its Border Color to clrBlack
          LotSize.SetBGColor(clrWhiteSmoke); // Setting its BG Color to clrWhiteSmoke
          LotSize.SetText("0.01"); // Setting its text to 0.01
          LotSize.SetTextColor(clrBlack); // Setting its text color to clrBlack
          
          BuyButton.Create("BuyButton", 110, 180, 80, 25); // Creating the BuyButton with specified dimensions
          BuyButton.SetBorderColor(clrBlack); // Setting its Border Color to clrBlack
          BuyButton.SetText("Buy"); // Setting its text to "Buy"
          BuyButton.SetBGColor(clrLime); // Setting its BG Color to clrLime
          
          SellButton.Create("SellButton", 210, 180, 80, 25); // Creating the SellButton with specified dimensions
          SellButton.SetBorderColor(clrBlack); // Setting its Border Color to clrBlack
          SellButton.SetText("Sell"); // Setting its text to "Sell"
          SellButton.SetBGColor(clrRed); // Setting its BG Color to clrRed
          
          ChartRedraw(0); // Redrawing the chart to reflect changes
          return(INIT_SUCCEEDED); // Indicating successful initialization
         }
      
      //+------------------------------------------------------------------+
      //| Expert deinitialization function                                 |
      //+------------------------------------------------------------------+
      void OnDeinit(const int reason)
         {
          MainDashboardBody.Destroy(); // Destroying the MainDashboardBody object
          TitleBar.Destroy(); // Destroying the TitleBar object
          TitleText.Destroy(); // Destroying the TitleText object
          LotSizeText.Destroy(); // Destroying the LotSizeText object
          LotSize.Destroy(); // Destroying the LotSize object
          BuyButton.Destroy(); // Destroying the BuyButton object
          SellButton.Destroy(); // Destroying the SellButton object
         }
      
      //+------------------------------------------------------------------+
      //| Chart event handling function                                    |
      //+------------------------------------------------------------------+
      void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
         {
          // Handles click events for Buy and Sell buttons and opens corresponding positions
          if(id == CHARTEVENT_OBJECT_CLICK) {
              double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
              double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
              if(sparam == "BuyButton") {
                  trade.PositionOpen(_Symbol, ORDER_TYPE_BUY, (double)LotSize.GetText(), ask, 0, 0);
              }
              if(sparam == "SellButton") {
                  trade.PositionOpen(_Symbol, ORDER_TYPE_SELL, (double)LotSize.GetText(), bid, 0, 0);
              }
          }
         }
      //+------------------------------------------------------------------+

      Изменения:

      1. Изменения цвета:

        • Изменен цвет фона строки заголовка на темно-синий с помощью TitleBar.SetBGColor(C'27, 59, 146').
        • Изменен цвет основного тела информационной панели на светло-синий с помощью MainDashboardBody.SetBGColor(C'102, 152, 250').
        • Изменен цвет текста заголовка на белый с помощью TitleText.SetTextColor(clrWhite).
        • Изменен цвет текста Lot Size на белый с помощью LotSizeText.SetTextColor(clrWhite).
      2. Включение торговой библиотеки:

        • Интегрировали торговую библиотеку и создали экземпляр с именем trade со следующим кодом:
          #include <Trade/Trade.mqh>
          CTrade trade;

      3. Создание функции OnChartEvent:

        Реализована функция OnChartEvent, которая немедленно выполняет соответствующий ордер при нажатии кнопки Buy или Sell. Код выглядит следующим образом:

        //+------------------------------------------------------------------+
        //| Chart event handling function                                    |
        //+------------------------------------------------------------------+
        void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
           {
            if(id == CHARTEVENT_OBJECT_CLICK) {
                double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
                double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
                if(sparam == "BuyButton") {
                    trade.PositionOpen(_Symbol, ORDER_TYPE_BUY, (double)LotSize.GetText(), ask, 0, 0);
                }
                if(sparam == "SellButton") {
                    trade.PositionOpen(_Symbol, ORDER_TYPE_SELL, (double)LotSize.GetText(), bid, 0, 0);
                }
            }
           }
        //+------------------------------------------------------------------+
        Если идентификатор события равен CHARTEVENT_OBJECT_CLICK, функция обнаруживает щелчок по объекту, получает имя этого объекта с помощью sparam, проверяет, является ли имя объекта BuyButton или SellButton, а затем размещает соответствующую сделку с помощью библиотеки Trade.

      Конечный результат: 


      Рис. 7. Завершенный советник Simple Trading (статический)
      Рис. 7. Завершенный советник Simple Trading (статический)


    Обсуждение перемещения нашей статической панели со всеми элементами внутри нее

    Теперь начинается настоящая работа. Как сделать всё перемещаемым?

    На данный момент мы можем сделать перемещаемым любой отдельный элемент. Но нам нужно, чтобы перемещаемыми были все элементы. Так давайте сделаем один элемент перемещаемым и заставим все остальные следовать за ним. Мы можем заставить элементы следовать за основным, используя CustomChartEvent, но, к сожалению, этот метод медленный и неэффективный. Я считаю, что наиболее эффективным подходом является перемещение нашего основного элемента (вокруг которого будут перемещаться все остальные элементы) и одновременное перемещение других элементов. Как реализовать эту идею на практике?

    Давайте назовем наш основной элемент Центральным (Central Element). Сделаем строку заголовка Центральным элементом. Теперь переместим все остальные элементы вокруг него.

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

    Вот наша текущая функция OnEvent:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void RectangleLabel::OnEvent(int id, long lparam, double dparam, string sparam)
      {
       //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
       if(id == CHARTEVENT_MOUSE_MOVE)
         {
          //define X, Y, XDistance, YDistance, XSize, YSize
          int X = (int)lparam;
          int Y = (int)dparam;
          int MouseState = (int)sparam;
    
          string name = Name;
          int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
          int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
          int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
          int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
    
          if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click
            {
             mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X
             mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y
             mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance
             mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance
    
             if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard
               {
                movingState = true; //If yes the set movingState to True
               }
    
            }
    
          if(movingState)//if movingState is true, Update the Dashboard position
            {
             ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse
             ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX)
             ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY)
             ChartRedraw(0); //Redraw Chart
            }
    
          if(MouseState == 0)//Check if MLB is not pressed
            {
             movingState = false;//set movingState again to false
             ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again
            }
    
          previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value
         }
      }
    //+------------------------------------------------------------------+

    Мы до сих пор не добавили эту функцию в класс RectangleLabel. Сделаем это после обсуждения подхода.

    Что же нам нужно для перемещения любого объекта? Его имя, верно?

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

    Всякий раз, когда мышь движется, мы устанавливаем XDis и YDis центрального элемента следующим образом:

    ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX)
    ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY)

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

    При ближайшем рассмотрении лучший подход находится прямо у нас под носом. Нам просто нужно поддерживать "расстояние X и расстояние Y между центральными и другими элементами" (X Distance and Y Distance between Central Elements and Other Elements). Да, это так просто.

    Итак, укажем "X Distance and Y Distance between Central Elements and Other Elements" и сохраним это расстояние. Как нам записать эти расстояния? В какой-то момент мы добавим другие элементы к центральному элементу и в этот момент укажем "X Distance and Y Distance between Central Elements and Other Elements."

    Повторим: в какой-то момент мы будем использовать имена других элементов, чтобы добавить их к центральному элементу, и в этот момент мы сохраним и будем поддерживать расстояние X и расстояние Y между центральными и другими элементами. Мы обновим это расстояние после обновления положения центрального элемента.

    Реализуем этот подход на практике.


    Применение выбранного подхода на практике для создания перемещаемой панели

    Итак, давайте обсудим, где мы будем хранить имя, расстояние X и расстояние Y между центральными и другими элементами. Это единственные категории информации, которые нам нужно хранить.

    Мы создадим функцию Add в классе RectangleLabel. Используя эту функцию, мы будем хранить следующие две вещи:

    1. Имя в массиве addNames
    2. Расстояние X и расстояние Y между центральными и другими элементами в addXDisDifference и addYDisDifference соответственно.

    Что касается соглашений об именах, "added" (добавленная) подразумевает, что переменная связана с другим элементом, добавленным к центральному, тогда как "XDis" и "YDis" довольно просты. "Difference" (разность) предполагает, что переменная имеет какое-то отношение к разности. Тщательный выбор имени позволяет избежать путаницы.

    Объявим эти переменные:

    string           addedNamed[];
    int              addedXDisDiffrence[], addedYDisDiffrence[];

    Обратите внимание, что мы объявляем их частными (Private). Нам не нужно, чтобы они были общедоступными (Public). Кроме того, все они являются массивами.

    Теперь создадим функцию Add:

    //+------------------------------------------------------------------+
    //| Method to add an object by name to the rectangle label           |
    //+------------------------------------------------------------------+
    void RectangleLabel::Add(string name)
       {
        ArrayResize(addedNames, ArraySize(addedNames) + 1);
        ArrayResize(addedXDisDiffrence, ArraySize(addedXDisDiffrence) + 1);
        ArrayResize(addedYDisDiffrence, ArraySize(addedYDisDiffrence) + 1);
        
        addedNames[ArraySize(addedNames) - 1] = name;
        addedXDisDiffrence[ArraySize(addedXDisDiffrence) - 1] = ObjectGetInteger(0, _name, OBJPROP_XDISTANCE) - ObjectGetInteger(0, name, OBJPROP_XDISTANCE);
        addedYDisDiffrence[ArraySize(addedYDisDiffrence) - 1] = ObjectGetInteger(0, _name, OBJPROP_YDISTANCE) - ObjectGetInteger(0, name, OBJPROP_YDISTANCE);
       }
    //+------------------------------------------------------------------+

    Эта функция объявлена в классе RectangleLabel, поскольку TitleBar — наш центральный элемент и по сути объект RECTANGLE_LABEL. Очевидно, мы объявляем переменные в том же классе, поскольку используем их в этой функции.

    Эта функция принимает имя в качестве параметра, а затем увеличивает размер этих трех массивов на один. По последнему индексу мы сохраняем соответствующие данные. В качестве Name мы просто сохраняем имя. Для разности расстояний (X и Y) мы сохраняем разность центрального элемента (в данном случае TitleBar) и элемента, имя которого указано в качестве параметра. Это составляет нашу функцию Add.

    Далее нам нужно изменить функцию OnEvent. Мы создаем цикл для перебора массива addNames и поддерживаем расстояние между TitleBar и именованным элементом, устанавливая его равным новому расстоянию X/Y TitleBar минус значение разности, указанное в соответствующих массивах.

    for(int i = 0; i < ArraySize(addedNames); i++)
       {
        ObjectSetInteger(0, addedNames[i], OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX - addedXDisDiffrence[i]);
        ObjectSetInteger(0, addedNames[i], OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY - addedYDisDiffrence[i]);
       }

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

    Где мы разместим этот цикл? Сразу после обновления центрального элемента.

    Вот как выглядит наша новая функция OnEvent:

    //+------------------------------------------------------------------+
    //| Event handling for mouse movements                               |
    //+------------------------------------------------------------------+
    void RectangleLabel::OnEvent(int id, long lparam, double dparam, string sparam)
       {
        // Handle mouse movement events for dragging the rectangle label
        if(id == CHARTEVENT_MOUSE_MOVE)
           {
            int X = (int)lparam;
            int Y = (int)dparam;
            int MouseState = (int)sparam;
    
            string name = _name;
            int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE);
            int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE);
            int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE);
            int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE);
    
            if(previousMouseState == 0 && MouseState == 1)
               {
                mlbDownX = X;
                mlbDownY = Y;
                mlbDownXDistance = XDistance;
                mlbDownYDistance = YDistance;
    
                if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize)
                   {
                    movingState = true;
                   }
    
               }
    
            if(movingState)
               {
                ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
                ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);
                ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);
                for(int i = 0; i < ArraySize(addedNames); i++)
                   {
                    ObjectSetInteger(0, addedNames[i], OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX - addedXDisDiffrence[i]);
                    ObjectSetInteger(0, addedNames[i], OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY - addedYDisDiffrence[i]);
                   }
                ChartRedraw(0);
               }
    
            if(MouseState == 0)
               {
                movingState = false;
                ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
               }
    
            previousMouseState = MouseState;
           }
       }

    Выделенная часть — это наш новый цикл.

    Теперь нам просто нужно использовать функцию Add, чтобы прикрепить элементы к центральному, поскольку мы выбрали TitleBar. Мы используем функцию Add из экземпляра TitleBar ("TitleBar").

    Воспользуемся функцией Add в экземпляре TitleBar, чтобы добавить все остальные элементы в TitleBar:

    // Add the other elements to the Central Element i.e. TitleBar object in this case
    TitleBar.Add("MainDashboardBody");
    TitleBar.Add("TitleText");
    TitleBar.Add("LotSizeText");
    TitleBar.Add("LotSize");
    TitleBar.Add("BuyButton");
    TitleBar.Add("SellButton");


    При этом имена всех этих элементов добавляются в массив addNames, что позволяет им перемещаться. Кроме того, отмечается их расстояние от TitleBar, поэтому расстояние будет сохраняться.

    Теперь давайте воспользуемся функцией OnEvent. Без нее всё было бы напрасно.

    // Passes events to the TitleBar object
    TitleBar.OnEvent(id, lparam, dparam, sparam);
    Добавляем ее в OnChartEvent(), и на этом всё. Код довольно длинный, но конечный результат должен стоить затраченных усилий.

    Рис. 8. Конечный результат

    Рис. 8. Конечный результат



    Заключение

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

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

    Удачного программирования! Удачной торговли!

    Перевод с английского произведен MetaQuotes Ltd.
    Оригинальная статья: https://www.mql5.com/en/articles/12923

    Прикрепленные файлы |
    RectangleLabel.mqh (5.75 KB)
    Label.mqh (2.35 KB)
    Edit.mqh (2.53 KB)
    Button.mqh (2.31 KB)
    Последние комментарии | Перейти к обсуждению на форуме трейдеров (14)
    Stanislav Korotky
    Stanislav Korotky | 10 мар. 2024 в 14:49
    Aleksandr Slavskii #:

    Но это всё равно как то громоздко и не универсально.

    В следующей панельке всё равно по новой придётся высчитывать каждое поле и писать обработку для этих полей(кнопки/поле ввода).

    И я что то ну никак не могу придумать схемку как сделать это всё более универсально.

    Может, чего доброго подскажите по оформлению кода?

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

    Maxim Kuznetsov
    Maxim Kuznetsov | 10 мар. 2024 в 15:13
    Stanislav Korotky #:

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

    вспомнилось :-)

    для тех кому не для маркета, хочется более-менее сложного но красивого, и DLL при этом писать лень, существует GtkServer https://sourceforge.net/projects/gtk-server/ и к нему дизайнер форм Glade

    методика: GtkServer запускается как tcp listener, советник используя SocketOpen/SocketSend отсылает текстом "загрузить форму" (или сам по шагам формирует gtk виджеты) и так-же читает результат..

    Aleksandr Slavskii
    Aleksandr Slavskii | 10 мар. 2024 в 17:13
    Stanislav Korotky #:

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

    Станислав, ну ё-ма-ё, под статьёй для начинающих скидываете ссылку на статью для продвинутых  :)

    Почитал, не прочитал, а всего лишь почитал, так как мой уровень в программировании намного ниже, чем нужен для понимания того, что там написано.

    Пользоваться тем, чего не понимаю, стараюсь как можно меньше. Недавняя ситуация ещё сильнее укоренила меня в этом.

    Вы ведь наверняка читаете тему «Ошибки, баги, вопросы», так вот, у меня была задача вывести стрелочки от закрытых позиций на ренко-график, вход-выход между ними — линия.

    Решил сам не писать, а взять готовое от Сайбера, как итог — полдня потерянного времени. Сайбер свой код в итоге поправил, но время-то я потерял.

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

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

    ООП для этого, на мой взгляд, вообще ни к чему. Я его не знаю, поэтому не люблю.

    Принцип MVC для моих целей вполне подходит, если я его правильно понял)))

    В общем-то картинка, как это должно быть, у меня уже сложилась.


    Кстати, подскажите, как сделать, чтоб когда я обращаюсь из программы к функциям класса-наследника, не видеть функции базового класса? Ну, если это возможно. 

    Aleksandr Slavskii
    Aleksandr Slavskii | 10 мар. 2024 в 17:20
    Maxim Kuznetsov #:

    вспомнилось :-)

    для тех кому не для маркета, хочется более-менее сложного но красивого, и DLL при этом писать лень, существует GtkServer https://sourceforge.net/projects/gtk-server/ и к нему дизайнер форм Glade

    методика: GtkServer запускается как tcp listener, советник используя SocketOpen/SocketSend отсылает текстом "загрузить форму" (или сам по шагам формирует gtk виджеты) и так-же читает результат..

    И вы туда же :)

    Да я вообще слова типа   tcp listener, SocketOpen/SocketSend  воспринимаю как матершинные, без гугла даже их значение не знаю, а вы предлагаете ещё и воспользоваться этим.

    Господа продвинутые, ну имейте совесть, хватит пугать людей своей терминологией)))

    Stanislav Korotky
    Stanislav Korotky | 11 мар. 2024 в 12:10
    Aleksandr Slavskii #:

    Станислав, ну ё-ма-ё, под статьёй для начинающих скидываете ссылку на статью для продвинутых  :)

    Почитал, не прочитал, а всего лишь почитал, так как мой уровень в программировании намного ниже, чем нужен для понимания того, что там написано.

    Пользоваться тем, чего не понимаю, стараюсь как можно меньше. Недавняя ситуация ещё сильнее укоренила меня в этом.

    С ПО, к сожалению, вырисовывается такая ситуация, что невозможно самому во всем разобраться. По такой логике и операционку нужно под себя "пилить" (чем некоторые апологеты Линукса занимаются).

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

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

    Нейросети — это просто (Часть 66): Проблематика исследования в офлайн обучении Нейросети — это просто (Часть 66): Проблематика исследования в офлайн обучении
    Обучение моделей в офлайн режиме осуществляется на данных ранее подготовленной обучающей выборки. Это дает нам ряд преимуществ, но при этом информация об окружающей среде сильно сжимается до размеров обучающей выборки. Что, в свою очередь, ограничивает возможности исследования. В данной статье хочу предложить познакомиться с методом, позволяющем наполнить обучающую выборку максимально разнообразными данными.
    Изучение MQL5 от новичка до профи (Часть I): Начинаем программировать Изучение MQL5 от новичка до профи (Часть I): Начинаем программировать
    Эта статья является вводной для целого цикла статей о программировании. Здесь предполагается, что читатель вообще не сталкивался с программированием раньше. Поэтому начинаю я с самых основ. Уровень знания программирования: абсолютный новичок.
    Популяционные алгоритмы оптимизации: Метод Нелдера-Мида, или метод симплексного поиска (Nelder–Mead method, NM) Популяционные алгоритмы оптимизации: Метод Нелдера-Мида, или метод симплексного поиска (Nelder–Mead method, NM)
    Статья представляет полное исследование метода Нелдера-Мида объясняя, как симплекс — пространство параметров функции — изменяется и перестраивается на каждой итерации для достижения оптимального решения, а также описывает способ улучшения этого метода.
    Python, ONNX и MetaTrader 5: Создаем модель RandomForest с предварительной обработкой данных RobustScaler и PolynomialFeatures Python, ONNX и MetaTrader 5: Создаем модель RandomForest с предварительной обработкой данных RobustScaler и PolynomialFeatures
    В этой статье мы создадим модель случайного леса на языке Python, обучим модель и сохраним ее в виде конвейера ONNX с препроцессингом данных. Модель мы далее используем в терминале MetaTrader 5.