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

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

MetaTrader 5Трейдинг | 24 июля 2023, 13:01
1 068 2
Kailash Bai Mina
Kailash Bai Mina

Введение

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

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

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

План действий таков:


Расшифровка событий графика: Строительные блоки перемещаемого графического интерфейса

На данный момент код советника выглядит так (то есть это абсолютно базовый советник):

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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
   {
//---
    
//---
    return(INIT_SUCCEEDED);
   }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
   {
//---

   }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
   {
//---

   }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
   {
//---
   }
//+------------------------------------------------------------------+

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

Предустановленные функции Назначение
OnInit()
Запуск при инициализации, то есть советник инициализируется (прикрепляется) на графике. Запускается только один раз
OnTick()
Запуск при вохдящем тике, то есть когда символ на графике получает тик от брокера. Тик означает обновление цены
OnDeinit() Запуск при деинициализации, то есть когда советник деинициализируется (удаляется) с графика. Запускается только один раз

Как и предыдущая функция, OnChartEvent() выполняется, когда происходят определенные события. О каких событиях идет речь?

Есть 9 предопределенных событий (исключая 2 кастомных):

  1. CHARTEVENT_KEYDOWN
  2. CHARTEVENT_MOUSE_MOVE 
  3. CHARTEVENT_OBJECT_CREATE 
  4. CHARTEVENT_OBJECT_CHANGE
  5. CHARTEVENT_OBJECT_DELETE 
  6. CHARTEVENT_CLICK 
  7. CHARTEVENT_OBJECT_CLICK 
  8. CHARTEVENT_OBJECT_DRAG 
  9. CHARTEVENT_OBJECT_ENDEDIT

Краткий обзор событий, использующихся в статье:

  1. CHARTEVENT_KEYDOWN

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

    Удержание клавиши означает нажатие со скоростью 30 кликов в секунду.

    Что мы можем сделать при нажатии кнопки? Пока немного. Для начала мы должны узнать, какая клавиша была нажата. Как этого добиться? В этом нам помогут параметры OnChartEvent().

    О каких параметрах идет речь? Есть 4 параметра, которые мы получаем при выполнении OnChartEvent().

    1. id -> integer
    2. lparam -> long
    3. dparam -> double
    4. sparam -> string

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

    Например, в случае события CHARTEVENT_KEYDOWN,

    • id содержит сам CHARTEVENT_KEYDOWN, чтобы мы могли определить, для какого события вызывается OnChartEvent(), и соответствующим образом обрабатывать другие параметры.
    • lparam содержит код нажатой клавиши.
    • dparam содержит количество нажатий клавиш, сгенерированных, когда клавиша удерживалась в нажатом состоянии. Когда мы удерживаем клавишу, dparam делает 30 кликов в секунду. Это значение всегда равно 1.
    • sparam содержит битовую маску. Проще говоря, параметр описывает состояние клавиши, нажатой или удерживаемой, показывая 2 разных значения для конкретной клавиши (см. пример ниже).


    Например, мы нажали/удерживаем клавишу A на клавиатуре, тогда OnChartEvent() будет выполняться с

    • id =  CHARTEVENT_KEYDOWN
    • lparam = 65
    • dparam = 1
    • sparam = 30 для первого клика и 16414 для непрерывных последующих кликов при удержании A со скоростью 30 кликов в секунду.


    Теперь, когда у нас есть информация, мы можем использовать некоторые операторы if и что-то делать, когда пользователь нажимает или удерживает клавишу A.



  2. CHARTEVENT_MOUSE_MOVE

    Прежде всего, требуется, чтобы имена свойств булевого графика CHART_EVENT_MOUSE_MOVE были установлены в True. Это можно сделать с помощью простой строки кода:

    //Set Chart property CHART_EVENT_MOUSE_DOWN to true 
    ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);

    Обычно рекомендуется делать это в OnInit(). Как только это будет сделано, мы можем использовать CHARTEVENT_MOUSE_MOVE

    Всякий раз, когда мышь перемещается над графиком, OnChartEvent() будет выполняться с приведенной ниже информацией в параметрах:

    • id =  CHARTEVENT_MOUSE_MOVE
    • lparam = координата по оси X
    • dparam = координата по оси Y
    • sparam =  значение битовой маски, описывающее статус кнопок мыши

     Битовая маска содержит следующие значения -> 

    • Левая кнопка мыши               --> 1
    • Правая кнопка мыши             --> 2
    • Средняя кнопка мыши           --> 16
    • Первая клавиша X мыши     --> 32
    • Вторая клавиша X мыши --> 64
    • Клавиша Shift                  --> 4
    • Клавиша управления              --> 8

    Окно графика находится в четвертом квадранте, то есть координата по оси X (lparam) находится слева от окна графика, а координата по оси Y (dparam) — сверху окна графика. Теперь, со всей этой информацией, мы готовы использовать CHARTEVENT_MOUSE_MOVE. Мы будем использовать его ниже, чтобы сделать графический интерфейс подвижным.


  3. CHARTEVENT_CLICK

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

    • id =  CHARTEVENT_CLICK
    • lparam = координата по оси X
    • dparam = координата по оси Y
    • sparam =  "" то есть пустая строка, означающая, что она не содержит никакой полезной информации


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


Выше мы обсудили три события, которые вызывают функцию OnChartEvent() - CHARTEVENT_KEYDOWN, CHART_EVENT_MOUSE_MOVE, CHARTEVENT_CLICK.

Если вы раньше не использовали функцию OnChartEvent(), всё это может показаться немного запутанным. Да, мы всё это делали и преодолели этот этап, узнавая больше и практикуясь. Мы используем приведенные выше знания, чтобы сделать графический интерфейс перемещаемым. Очень скоро почувствуете себя увереннее.

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


Разработка перемещаемого графического интерфейса: Пошаговое руководство

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

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

Дочитайте статью до конца и оцените сами, насколько сложным был путь. Назовем наш графический интерфейс панелью (dashboard). Скоро это действительно будет панель.

Во-первых, давайте создадим базовую прямоугольную форму 200x200 (XSize x YSize) на расстоянии 100 пикселей слева (XDistance) и 100 пикселей сверху (YDistance) 

int OnInit()
   {
    //---
    //Set the name of the rectangle as "TestRectangle"
    string name = "TestRectangle";
    //Create a Rectangle Label Object at (time1, price1)=(0,0)
    ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
    //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
    ObjectSetInteger(0, name,OBJPROP_XDISTANCE, 100);
    //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
    ObjectSetInteger(0, name,OBJPROP_YDISTANCE, 100);
    //Set XSize to 200px i.e. Width of Rectangle Label
    ObjectSetInteger(0, name,OBJPROP_XSIZE, 200);
    //Set YSize to 200px i.e. Height of Rectangle Label
    ObjectSetInteger(0, name,OBJPROP_YSIZE, 200);
    //---
    return(INIT_SUCCEEDED);
   }


Теперь, когда мы запускаем советник на графике, мы должны увидеть нашу созданную прямоугольную форму:


Рис. 1. Простая прямоугольная форма

Рис. 1. Простая прямоугольная форма



Если мы сможем перемещать форму мышью, мы сможем сделать и много других вещей, в частности перемещать очень сложную множественную панель инструментов в окне графика, что может очень пригодится советникам/индикаторам. В качестве примера такого приложения можно назвать Trade Assistant EA.

Как же сделать форму перемещаемой? Сначала составим план:

  • Условия перед перемещением:
    • Мышь должна быть на панели

    • Левая кнопка мыши должна быть нажата

  • Если мы перемещаем мышь с нажатой левой кнопкой, панель должна двигаться.

  • Но на какое расстояние? Панель должна двигаться ровно до тех пор, пока удовлетворяются оба условия.

    Теперь напишем код шаг за шагом:

    В соответствии с первым условием мышь должна быть на панели. Сначала нам нужно найти X и Y-координаты положения мыши.


    Пришло время применить теорию. Для координат мыши по осям X и Y нам нужно использовать OnChartEvent().

    1. Установим свойство графика CHART_EVENT_MOUSE_MOVE на True

      Помещаем приведенный ниже код в OnInit(), чтобы сделать его истинным при инициализации советника:

      //Set Chart property CHART_EVENT_MOUSE_DOWN to true
      ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);

    2. Теперь мы можем получить координаты мыши в OnChartEvent().
      void OnChartEvent(const int id, const long& lparam, const double& dparam, const 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)
             {
              //Comment the X and Y Axes Coordinates
              Comment("X: ", lparam, "\nY: ", dparam);
             }
         }

      В OnChartEvent() мы сначала проверяем, что событие, вызвавшее OnChartEvent, было CHARTEVENT_MOUSE_MOVE, используя простой оператор if, который проверяет, равен ли идентификатор CHARTEVENT_MOUSE_MOVE, потому что мы хотим выполнить наш код комментария только в этом случае.

      Затем мы комментируем координаты осей X и Y (они будут отображаться в левом верхнем углу окна графика белым цветом с небольшим шрифтом), например: 

      Рис. 2. X и Y-координаты

      Рис. 2. X и Y-координаты

    Теперь вернемся к нашей логике, позволяющей узнать, находится ли мышь на панели (см. изображение ниже): 

    Рис. 3. Визуализация формулы

    Рис. 3. Визуализация формулы





    Чтобы узнать, находится ли мышь на панели,

    • X >= XDistance             --> X>=100
    • X <= XDIstance + XSize --> X<=300
    • Y >= YDistance             --> Y>=100
    • Y <= YDistance + YSize --> Y>=300

    Преобразование в код:

    void OnChartEvent(const int id, const long& lparam, const double& dparam, const 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;
    
          string name = "TestRectangle";
          int XDistance = ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
          int YDistance = ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
          int XSize = ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
          int YSize = ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
          
          //Check Mouse on Dashboard condition
          if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize)
            {
             //Comment the X and Y Axes Coordinates and Mouse is on the dashboard
             Comment("X: ", lparam, "\nY: ", dparam, "\nMouse is on the Dashboard");
            }
          else
            {
             //Comment the X and Y Axes Coordinates and Mouse is not on the dashboard
             Comment("X: ", lparam, "\nY: ", dparam, "\nMouse is NOT on the Dashboard");
            }
    
         }
      }

    Определены переменные X, Y, name, XDistance, YDistance, XSize и YSize. Получены X из lparam, Y из dparam, name — это просто имя строки, которое мы установили выше, XDistance, YDistance, XSize, YSize с помощью функции ObjectGetInteger().

    Наша цель здесь - заставить приборную панель двигаться плавно.

    Результат:

    Рис. 4. Мышь на панели

    Рис. 4. Мышь на панели


    Как видите, всякий раз, когда мышь находится на панели, комментарий меняется. Итак, наша логика работает. Теперь мы знаем, находится ли мышь на панели или нет.

    Теперь нам понадобится состояние кнопок мыши. Как мы помним, если щелкнуть левой кнопкой мыши, sparam будет равен 1. Воспользуемся этим.

    void OnChartEvent(const int id, const long& lparam, const double& dparam, const 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 = "TestRectangle";
          int XDistance = ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
          int YDistance = ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
          int XSize = ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
          int YSize = ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
    
          //Check Dashboard move conditions
          if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize && MouseState == 1)
            {
             //Comment that the dashboard is ready to move
             Comment("Dashboard is ready to move.");
            }
          else
            {
             //Comment that the dashboard is not ready to move
             Comment("Dashboard is NOT ready to move.");
            }
          
         }
      }

    Я добавил

    int MouseState = (int)sparam; //To get the mouse state: 1 -> Mouse Left Button Down (You can check the other above)
    

    в переменные, а также условие

    if(MouseState == 1) // This insures that Mouse Left button in pressed

    в оператор if. Также немного изменил комментарии.

    Теперь всякий раз, когда левая кнопка мыши находится в нажатом состоянии на приборной панели, мы получаем комментарий "Dashboard is ready to move" (панель готова к перемещению). В противном случае получаем комментарий "Dashboard is NOT ready to move" (панель НЕ готова к перемещению).

    Посмотрим на это в действии:

    Рис. 5. Панель готова к перемещению

    Рис. 5. Панель готова к перемещению


    Обратите внимание на изменение комментария при удерживании левой кнопки мыши.


    Теперь мы готовы переместить нашу панель. Как мы это сделаем?

    Мы знаем, что панель будет перемещаться с помощью мыши. Как изменить положение панели? Конечно, с помощью свойств XDistance и YDistance. Итак, мы меняем XDistance и YDistance, но насколько? Так как панель движется с помощью мыши, она должна двигаться так же, как и мышь, верно?

    Итак, на сколько продвинулась наша мышь? Пока слишком много вопросов. Давайте составим план.

    План: 

    • Узнаем, насколько сильно сдвинулась наша мышь после нажатия ее левой кнопки
    • Переместим панель точно на ту же величину, на которую перемещается мышь.
    • Будем делать это до тех пор, пока нажата левая кнопка мыши, после этого перестанем перемещать панель.

    Пойдем шаг за шагом.

    Мы всегда можем получить правильную текущую позицию мыши. Что если мы сохраним позицию мыши на момент первого нажатия ее левой кнопки?

    Мы можем узнать, нажата ли левая кнопка мыши из созданной нами переменной MouseState, которая хранит sparam.

    Ниже приведен код, который обнаруживает первое нажатие левой кнопки мыши: 

    int previousMouseState = 0;
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id, const long& lparam, const double& dparam, const 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)
         {
          int MouseState = (int)sparam;
          
          bool mblDownFirstTime = false;
          if(previousMouseState == 0 && MouseState == 1) {
             mblDownFirstTime = true;
          }
          
          previousMouseState = MouseState;
         }
      }
    //+------------------------------------------------------------------+

    Я удалил некоторые строки для простоты восприятия. Этот метод довольно часто используется при создании функций трейлинга для советника, при обнаружении нового бара и т. д.

    Давайте разберем его:

    1. int previousMouseState = 0;

      Объявим переменную int с именем previousMouseState в глобальном пространстве и установим для нее значение 0. Эта переменная будет хранить значение MouseState с момента последнего события CHARTEVENT_MOUSE_MOVE. Как именно? Скоро узнаете.


    2. int previousMouseState = 0;

      Объявим переменную int с именем previousMouseState в глобальном пространстве и установим для нее значение 0. Эта переменная будет хранить значение MouseState с момента последнего события CHARTEVENT_MOUSE_MOVE. Как именно? Скоро узнаете.

    3. int MouseState = (int)sparam;   
      bool mblDownFirstTime = false;
      if(previousMouseState == 0 && MouseState == 1) {
         mblDownFirstTime = true;
      }


      Во-первых, мы объявляем один MouseState и устанавливаем его равным sparam, который содержит состояние мыши, во-вторых, мы объявляем логическую переменную с именем mblDownFirstTime и устанавливаем ее значение по умолчанию равным false.

      Затем мы проверяем 2 условия: одно значение previousMouseState должно быть равно 0 (левая кнопка мыши отпущена, кнопка мыши не нажата), а (&&) MouseState должно быть равно 1 (левая кнопка мыши нажата).

      Это условие в основном подтверждает или отрицает, что левая кнопка мыши нажата в первый раз. Если в первый раз, мы устанавливаем mblDownFirstTime равным true, чтобы мы могли использовать эту переменную позже.


    Наш первый шаг завершен, перейдем к шагам 2 и 3.

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

    1. Создадим логическую глобальную переменную movingState, которую мы установим в true, как только пользователь щелкнет левой кнопкой мыши на панели инструментов, а также объявим MLB Down X, MLB Down Y, MLB Down XDistance, MLB Down YDistance (здесь MLB Down означает, что левая кнопка мыши была отпущена первой). Они понадобятся для изменения положения панели инструментов.
    2. Пока movingState равен true, мы будем обновлять положение панели инструментов в соответствии с изменением положения мыши по сравнению с исходным (при первом нажатии левой кнопкой мыши).
    3. Пока movingState равен true, мы будем обновлять положение панели инструментов в соответствии с изменением положения мыши по сравнению с исходным (при первом нажатии левой кнопкой мыши).


    Новый код:

    int previousMouseState = 0;
    int mlbDownX = 0;
    int mlbDownY = 0;
    int mlbDownXDistance = 0;
    int mlbDownYDistance = 0;
    bool movingState = false;
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id, const long& lparam, const double& dparam, const 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)
         {
          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);
             ChartRedraw(0);
            }
    
          if(MouseState == 0)
            {
             movingState = false;
             ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
            }
    
          previousMouseState = MouseState;
         }
      }
    //+------------------------------------------------------------------+

     Давайте разберем это более простыми словами:

    1. int mlbDownX = 0;
      int mlbDownY = 0;
      int mlbDownXDistance = 0;
      int mlbDownYDistance = 0;
      bool movingState = false;

      Мы создали несколько переменных в глобальном пространстве. Я уже упоминал о предыдущем MouseState:

      • mlbDownX                -> Удерживать координату X, когда левая кнопка мыши нажата в первый раз
      • mlbDownY                -> Удерживать координату Y, когда левая кнопка мыши нажата в первый раз
      • mlbDownXDistance   -> Удерживать свойство XDistance панели инструментов при первом нажатии левой кнопки мыши
      • mlbDownYDistance   -> Удерживать свойство YDistance панели инструментов при первом нажатии левой кнопки мыши    
      • movingState             -> Сохранять true, если мы перемещаем панель, иначе false

      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;
          }
        }



      Сначала мы проверяем, является ли это первым щелчком левой кнопки мыши. Если это так, мы обновляем mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance до текущих X, Y, XDistance, YDistance соответственно. Мы будем использовать их позже.

      Затем мы проверяем, была ли левая кнопка мыши отпущена на панели. Если да, устанавливаем movingState в true.

    2. if(movingState)
        {
         ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
         ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);
         ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);
         ChartRedraw(0);
        }

      Если movingState равен true, изменяем XDistance и YDistance,

      X - mlbDownX // Change in Mouse X Position form the initial click
      and 
      Y - mlbDownY // Change in Mouse X Position form the initial click

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

      Мы также устанавливаем CHART_MOUSE_SCROLL в false, чтобы график не перемещался вместе с нашей панелью. Тогда мы, конечно, перерисовываем график, чтобы получить очень плавное движение.

    3. if(MouseState == 0)
        {
         movingState = false;
         ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
        }

      Теперь, как только мы отпускаем левую кнопку мыши, MouseState становится равен 0.

      Затем мы устанавливаем movingState в false и снова разрешаем перемещение графика, снова устанавливая для CHART_MOUSE_SCROLL значение true.



    Наш код почти завершен. Можете похвалить себя за терпение.

    Наш полный код выглядит так:

    //+------------------------------------------------------------------+ //| Expert initialization function                                   | //+------------------------------------------------------------------+ int OnInit()   { //---    //Set the name of the rectangle as "TestRectangle"    string name = "TestRectangle";    //Create a Rectangle Label Object at (time1, price1)=(0,0)    ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);    //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window    ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 100);    //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window    ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 100);    //Set XSize to 200px i.e. Width of Rectangle Label    ObjectSetInteger(0, name, OBJPROP_XSIZE, 200);    //Set YSize to 200px i.e. Height of Rectangle Label    ObjectSetInteger(0, name, OBJPROP_YSIZE, 200); //Set Chart property CHART_EVENT_MOUSE_DOWN to true    ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); //---    return(INIT_SUCCEEDED);   } //+------------------------------------------------------------------+ //| Expert deinitialization function                                 | //+------------------------------------------------------------------+ void OnDeinit(const int reason)   { //---   } //+------------------------------------------------------------------+ //| Expert tick function                                             | //+------------------------------------------------------------------+ void OnTick()   { //---   } //+------------------------------------------------------------------+ //Declare some global variable that will be used in the OnChartEvent() function int previousMouseState = 0; int mlbDownX = 0; int mlbDownY = 0; int mlbDownXDistance = 0; int mlbDownYDistance = 0; bool movingState = false; //+------------------------------------------------------------------+ //|                                                                  | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long& lparam, const double& dparam, const 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 = "TestRectangle";       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      }   } //+------------------------------------------------------------------+

    Этот простой код работает.

    Результат:

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

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



    Заключение

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

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

    К концу этого путешествия вы приобретете ценный навык создания и управления перемещаемым графическим интерфейсом в MQL5 - мощным инструментом в арсенале любого трейдера.


    Надеюсь, статья вам понравилась и хоть немного помогла.

    Удачи в написании кода!



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

    Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
    Aleksandr Slavskii
    Aleksandr Slavskii | 24 июл. 2023 в 17:33

    Статья понравилась. Спасибо.

    Жду продолжения.


    P.S. Три года назад вы абсолютно не знали MQL, а теперь пишете статьи по программированию. Вы большой молодец!!!

    Kailash Bai Mina
    Kailash Bai Mina | 26 июл. 2023 в 19:50
    Aleksandr Slavskii #:

    Статья понравилась. Спасибо.

    Жду продолжения.


    P.S. Три года назад вы абсолютно не знали MQL, а теперь пишете статьи по программированию. Вы большой молодец!!!

    Я рад, что вам понравилось.

    Спасибо за комментарий, Вы мотивировали меня написать следующую статью.

    PS: Возможно, вам захочется ознакомиться со второй частью, хотя она еще не переведена. Еще раз спасибо.
    Структуры в MQL5 и способы вывода их данных на печать Структуры в MQL5 и способы вывода их данных на печать
    В статье рассмотрим структуры MqlDateTime, MqlTick, MqlRates, MqlBookInfo и способы вывода данных этих структур на печать. Для того, чтобы распечатать все поля структуры есть стандартная функция ArrayPrint(), которая выводит в удобном табличном формате данные, содержащиеся в массиве с типом обрабатываемой структуры.
    Брутфорс-подход к поиску закономерностей (Часть V): Взгляд с другой стороны Брутфорс-подход к поиску закономерностей (Часть V): Взгляд с другой стороны
    В статье я покажу совершенно иной подход к алготрейдингу, к которому мне пришлось прийти спустя достаточно длительное время. Конечно же все это связано с моей брутфорс программой, которая претерпела ряд изменений, которые позволяют ей решать одновременно несколько задач. Тем не менее статья получилась больше общей и максимально простой, по этому годится и для тех кто не в теме или просто проходил мимо.
    Нейросети — это просто (Часть 51): Актор-критик, управляемый поведением (BAC) Нейросети — это просто (Часть 51): Актор-критик, управляемый поведением (BAC)
    В последних двух статьях рассматривался алгоритм Soft Actor-Critic, который включает энтропийную регуляризацию в функцию вознаграждения. Этот подход позволяет балансировать исследование среды и эксплуатацию модели, но он применим только к стохастическим моделям. В данной статье рассматривается альтернативный подход, который применим как для стохастических, так и для детерминированных моделей.
    Дискретное преобразование Хартли Дискретное преобразование Хартли
    В этой статье мы познакомимся с одним из методов спектрального анализа и обработки сигналов - дискретным преобразованием Хартли. С его помощью можно фильтровать сигналы, анализировать их спектр и многое другое. Возможности DHT ничуть не меньше, чем у дискретного преобразования Фурье. Однако, в отличие от него, DHT использует только вещественные числа, что делает его более удобным для реализации на практике, а результаты его применения более наглядными.