
Сделайте торговые графики лучше с интерактивным графическим интерфейсом на основе MQL5 (Часть III): Простой перемещаемый торговый интерфейс
Введение
Для начала давайте вспомним, что мы рассмотрели в предыдущих двух частях:
1. В первой части мы рассмотрели концепцию событий графика, а затем создали две простые перемещаемые панели на одном графике.
2. Во второй части мы пошли еще дальше. Мы использовали классы в файле .mqh, чтобы сделать наш код более эффективным и универсальным, готовым к интеграции с полномасштабными советниками/индикаторами.
В третьей части мы сосредоточимся на улучшении наших панелей путем интеграции в них графических интерфейсов. Без графического интерфейса панели не будут служить своему прямому назначению.
Вот краткий обзор того, что мы рассмотрим в этой статье:
- Что мы создаем?
- Создание простой статической торговой панели
- Обсуждение перемещения нашей статической панели со всеми элементами внутри нее
- Применение выбранного подхода на практике для создания перемещаемой панели
- Заключение
Что мы создаем?
Мы хотим сделать перемещаемую панель с графическим интерфейсом, и для этого нам нужно решить, что мы будем создавать. В качестве основы я выбрал простой советник, а именно Simple Trading.
Во-первых, нам нужно создать эту статическую панель управления, то есть советник Simple Trading. Крайне важно сделать это эффективно, поскольку мы создаем полноценный советник. Под эффективностью я подразумеваю, что мы не можем просто открыть файл и написать туда весь код. Вместо этого нам нужен хорошо продуманный план, который позволит нам написать минимальный код в нескольких хорошо организованных .mqh-файлах. Самое главное, мы должны избегать многократного дублирования одного и того же кода для создания необходимых статических графических интерфейсов для нашей перемещаемой панели.
Вот базовая статическая панель, которую мы возьмем за основу:
Рис 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) { } //+------------------------------------------------------------------+
Давайте теперь составим план. Мы построим нашу статическую панель в следующем порядке:
- Строка заголовка
- Основная часть панели
- Текст заголовка
- Текст "Lot Size:" (размер лота)
- Поле редактирования
- Кнопки Buy и Sell
- Необходимые завершающие штрихи
Всё выглядит логично. Давайте начнем.
- Строка заголовка
Чтобы создать строку заголовка, нам нужно использовать объект 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) { } //+------------------------------------------------------------------+
Нам понадобятся некоторые функции:
- Create -> Создание прямоугольной метки
- Destroy -> Удаление панели
- SetBorderType -> Тип границы
- 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. Строка заголовка
- Основная часть панели
Снова воспользуемся классом 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. Добавлено основное тело панели
- Текст заголовка
Чтобы добавить текст заголовка, нам нужно создать класс, аналогичный 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. Добавлен текст заголовка
- Текст "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.
- Поле редактирования
Для поля редактирования мы создадим класс, очень похожий на класс 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.
- Кнопки 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 - Последние штрихи
- Изменим цвет строки заголовка на темно-синий
- Изменим цвет тела основной панели на светло-голубой
- Изменим цвет текста заголовка с черного на белый
- Изменим цвет текста Lot Size с черного на белый
- Добавить функцию покупки/продажи
-
Изменения цвета:
- Изменен цвет фона строки заголовка на темно-синий с помощью TitleBar.SetBGColor(C'27, 59, 146').
- Изменен цвет основного тела информационной панели на светло-синий с помощью MainDashboardBody.SetBGColor(C'102, 152, 250').
- Изменен цвет текста заголовка на белый с помощью TitleText.SetTextColor(clrWhite).
- Изменен цвет текста Lot Size на белый с помощью LotSizeText.SetTextColor(clrWhite).
-
Включение торговой библиотеки:
- Интегрировали торговую библиотеку и создали экземпляр с именем trade со следующим кодом:
#include <Trade/Trade.mqh> CTrade trade;
- Интегрировали торговую библиотеку и создали экземпляр с именем trade со следующим кодом:
-
Создание функции 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.
На этом всё. Результат:
Рис. 5. Добавлен текст "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); } } } //+------------------------------------------------------------------+
Изменения:
Конечный результат:
Рис. 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. Используя эту функцию, мы будем хранить следующие две вещи:
- Имя в массиве addNames
- Расстояние 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. Конечный результат
Заключение
Мы успешно достигли целей, которые поставили перед собой в первых двух частях серии "Перемещаемый интерфейс", воплотив в жизнь динамичный и удобный интерфейс для торговых графиков. Спасибо, что нашли время прочитать мои статьи! Надеюсь, они окажутся полезными в ваших начинаниях.
Если у вас есть какие-либо идеи или предложения относительно того, что вы хотели бы видеть в моей следующей статье, напишите мне.
Удачного программирования! Удачной торговли!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/12923





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Но это всё равно как то громоздко и не универсально.
В следующей панельке всё равно по новой придётся высчитывать каждое поле и писать обработку для этих полей(кнопки/поле ввода).
И я что то ну никак не могу придумать схемку как сделать это всё более универсально.
Может, чего доброго подскажите по оформлению кода?
Наиболее практичный и универсальный вариант - дизайнить формы визуально, не заботясь о коде раскладки вообще - этим как раз должны заниматься классы. Одно из возможных решений было в статье.
Наиболее практичный и универсальный вариант - дизайнить формы визуально, не заботясь о коде раскладки вообще - этим как раз должны заниматься классы. Одно из возможных решений было в статье.
вспомнилось :-)
для тех кому не для маркета, хочется более-менее сложного но красивого, и DLL при этом писать лень, существует GtkServer https://sourceforge.net/projects/gtk-server/ и к нему дизайнер форм Glade
методика: GtkServer запускается как tcp listener, советник используя SocketOpen/SocketSend отсылает текстом "загрузить форму" (или сам по шагам формирует gtk виджеты) и так-же читает результат..
Наиболее практичный и универсальный вариант - дизайнить формы визуально, не заботясь о коде раскладки вообще - этим как раз должны заниматься классы. Одно из возможных решений было в статье.
Станислав, ну ё-ма-ё, под статьёй для начинающих скидываете ссылку на статью для продвинутых :)
Почитал, не прочитал, а всего лишь почитал, так как мой уровень в программировании намного ниже, чем нужен для понимания того, что там написано.
Пользоваться тем, чего не понимаю, стараюсь как можно меньше. Недавняя ситуация ещё сильнее укоренила меня в этом.
Вы ведь наверняка читаете тему «Ошибки, баги, вопросы», так вот, у меня была задача вывести стрелочки от закрытых позиций на ренко-график, вход-выход между ними — линия.
Решил сам не писать, а взять готовое от Сайбера, как итог — полдня потерянного времени. Сайбер свой код в итоге поправил, но время-то я потерял.
А если бы я захотел взять код из вашей статьи и мне понадобилось бы что-то подправить, ну, в общем, вы понимаете, ничего хорошего из этого не вышло.
У меня задача попроще, чем поставили себе вы при написании статьи. Мне просто нужно сделать так, чтоб следующую панельку я мог собирать из кусков готового кода, как дети из лего строят домики.
ООП для этого, на мой взгляд, вообще ни к чему. Я его не знаю, поэтому не люблю.
Принцип MVC для моих целей вполне подходит, если я его правильно понял)))
В общем-то картинка, как это должно быть, у меня уже сложилась.
Кстати, подскажите, как сделать, чтоб когда я обращаюсь из программы к функциям класса-наследника, не видеть функции базового класса? Ну, если это возможно.
вспомнилось :-)
для тех кому не для маркета, хочется более-менее сложного но красивого, и DLL при этом писать лень, существует GtkServer https://sourceforge.net/projects/gtk-server/ и к нему дизайнер форм Glade
методика: GtkServer запускается как tcp listener, советник используя SocketOpen/SocketSend отсылает текстом "загрузить форму" (или сам по шагам формирует gtk виджеты) и так-же читает результат..
И вы туда же :)
Да я вообще слова типа tcp listener, SocketOpen/SocketSend воспринимаю как матершинные, без гугла даже их значение не знаю, а вы предлагаете ещё и воспользоваться этим.
Господа продвинутые, ну имейте совесть, хватит пугать людей своей терминологией)))
Станислав, ну ё-ма-ё, под статьёй для начинающих скидываете ссылку на статью для продвинутых :)
Почитал, не прочитал, а всего лишь почитал, так как мой уровень в программировании намного ниже, чем нужен для понимания того, что там написано.
Пользоваться тем, чего не понимаю, стараюсь как можно меньше. Недавняя ситуация ещё сильнее укоренила меня в этом.
С ПО, к сожалению, вырисовывается такая ситуация, что невозможно самому во всем разобраться. По такой логике и операционку нужно под себя "пилить" (чем некоторые апологеты Линукса занимаются).
Поэтому современный подход заключается в том, чтобы взять готовые кирпичики (которые делают более-менее то, что нужно) и состыковать (не углубляясь в реализацию).
Я всего лишь отвечал на вопрос, как сделать более универсально. С тем, что моя статья сложная, не поспоришь, но суть то её в том, что можно (не вникая в устройство) описать нужный GUI как шаблон и без программирования, только подключив инклуды, получить контролы, перетаскивание окон, ресайз и прочее.