English 中文 Español Deutsch 日本語 Português
Графические интерфейсы I: Функции для кнопок формы и удаление элементов интерфейса (Глава 4)

Графические интерфейсы I: Функции для кнопок формы и удаление элементов интерфейса (Глава 4)

MetaTrader 5Примеры | 11 января 2016, 16:07
6 993 0
Anatoli Kazharski
Anatoli Kazharski

Содержание

 

Введение

Эта статья является продолжением первой части серии о графических интерфейсах. Более подробно, для чего предназначена эта библиотека, можно прочитать в самой первой статье: Графические интерфейсы I: Подготовка структуры библиотеки (Глава 1). Полный список ссылок на статьи первой части находится в конце каждой главы и там же есть возможность загрузить к себе на компьютер полную версию библиотеки на текущей стадии разработки. Файлы нужно разместить по тем же директориям, как они расположены в архиве.

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

 

Функции для кнопок формы

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

  1. Первая кнопка справа должна содержать в себе функцию закрытия окна. Здесь следует уточнить, как именно это будет реализовано. Если нажатие на эту кнопку было в диалоговом окне, которое было вызвано из главного окна или другого диалогового окна, то тогда это диалоговое окно будет закрываться. Если же нажатие на эту кнопку было в главном окне, то тогда программа будет удаляться с графика.

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

  3. Третья кнопка предназначена для включения режима пользовательских всплывающих подсказок. Здесь нужно уточнить, что подразумевается под «пользовательскими всплывающими подсказками». Язык MQL позволяет отображать текстовые всплывающие подсказки для каждого графического объекта, который находится в фокусе курсора мыши.

    Вы уже должны были заметить, что в коде класса CWindow в методах создания каждого объекта формы при указании свойств объекта использовался метод CChartObject::Tooltip(). Если нужно, чтобы у объекта была всплывающая подсказка, то в метод Tooltip() нужно передать текст, который должен отображаться при наведении курсора на объект. Если нужно, чтобы всплывающей подсказки не было вообще, то нужно передать текст "\n".

    У этого свойства есть ограничения: (1) нельзя отобразить весь текст, в котором больше 128-ми символов и (2) нельзя управлять свойствами текста (жирность, цвет, шрифт). Так вот, это не пользовательские всплывающие подсказки. Будем их относить к штатным подсказкам языка MQL. Такие подсказки хороши для небольших пояснений, но иногда нужно отобразить текст существенно большего объема в несколько строк, выделяя некоторые слова.

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

Рис. 1. Всплывающие подсказки в Excel 2010

Рис. 1. Всплывающие подсказки в Excel 2010

Итак, начнем с кнопки для закрытия окна. В классе CWindow создадим метод CloseWindow(). Вызываться он будет в обработчике событий графика CWindow::OnEvent() в теле условия обработки нажатия на графическом объекте по идентификатору CHARTEVENT_OBJECT_CLICK. Событие с этим идентификатором генерируется при нажатии на любом графическом объекте. При этом строковой параметр событий графика (sparam) содержит в себе имя объекта, по которому осуществили нажатие. Поэтому нужно делать проверку по имени объекта, на который нажали, чтобы убедиться, что это именно тот объект, который нужен.

Метод CWindow::CloseWindow() будет принимать один параметр — имя объекта, на который нажали. И в самом начале кода метода будет проверка — было ли нажатие на объекте, который по нашему замыслу является кнопкой для закрытия окна. Далее код метода разделяется на две ветки: для главного и диалогового окон. Так как многооконный режим у нас пока не готов полностью, ветку для диалогового окна оставим пустой. Вернемся к ней чуть позже для заполнения. А для работы с главным окном уже все готово, поэтому в теле этого условия нужны проверки, какого типа программа (эксперт или индикатор), так как для удаления разных типов программ используются разные функции языка MQL.

Язык MQL предлагает свою функцию для вызова диалогового окна для подтверждения тех или иных действий пользователя. Это функция MessageBox(). Временно воспользуемся ей для подтверждения удаления эксперта с графика. А вот для удаления индикатора это окно вызвать нельзя, так как оно относится к модальному типу. Дело в том, что индикаторы выполняются в интерфейсном потоке и их нельзя тормозить. Когда будет реализован многооконный режим и элемент управления «Кнопка», то можно будет использовать свое диалоговое окно, то есть построенное средствами библиотеки.

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

Внесите дополнения в класс CWindow в тех местах, где это показано в листингах кода ниже.

Объявление и реализация метода CWindow::CloseWindow() для закрытия окна:

class CWindow: public CElement
  {
public:
   //--- Закрытие окна
   bool              CloseWindow(const string pressed_object);
  };
//+------------------------------------------------------------------+
//| Закрытие диалогового окна или программы                          |
//+------------------------------------------------------------------+
bool CWindow::CloseWindow(const string pressed_object)
  {
//--- Если было нажатие не на кнопке закрытия окна
   if(pressed_object!=m_button_close.Name())
      return(false);
//--- Если это главное окно
   if(m_window_type==W_MAIN)
     {
      //--- Если программа типа "Эксперт"
      if(CElement::ProgramType()==PROGRAM_EXPERT)
        {
         string text="Удалить программу с графика?";
         //--- Откроем диалоговое окно
         int mb_res=::MessageBox(text,NULL,MB_YESNO|MB_ICONQUESTION);
         //--- Если нажата кнопка "Да", то удалим программу с графика
         if(mb_res==IDYES)
           {
            ::Print(__FUNCTION__," > Программа была удалена с графика по Вашему решению!");
            //--- Удаление эксперта с графика
            ::ExpertRemove();
            return(true);
           }
        }
      //--- Если программа типа "Индикатор"
      else if(CElement::ProgramType()==PROGRAM_INDICATOR)
        {
         //--- Удаление индикатора с графика
         if(::ChartIndicatorDelete(m_chart_id,m_subwin,CElement::ProgramName()))
           {
            ::Print(__FUNCTION__," > Программа была удалена с графика по Вашему решению!");
            return(true);
           }
        }
     }
   //--- Если это диалоговое окно
   else if(m_window_type==W_DIALOG)
     {
     }
//---
   return(false);
  }

Вызов метода CWindow::CloseWindow() в обработчике событий графика:

//+------------------------------------------------------------------+
//| Обработчик событий графика                                       |
//+------------------------------------------------------------------+
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Обработка события нажатия на объекте
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Закрыть окно
      CloseWindow(sparam);
      return;
     }
  }

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

Рис. 2. Тест закрытия программы нажатием кнопки на форме.

Рис. 2. Тест закрытия программы нажатием кнопки на форме

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

  • Метод CWindow::RollUp() для сворачивания формы.
  • Метод CWindow::Unroll() для разворачивания формы.

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

Код методов CWindow::RollUp() и CWindow::Unroll() можно подробнее изучить в листинге ниже:

//+------------------------------------------------------------------+
//| Сворачивает окно                                                 |
//+------------------------------------------------------------------+
void CWindow::RollUp(void)
  {
//--- Заменить кнопку
   m_button_rollup.Timeframes(OBJ_NO_PERIODS);
   m_button_unroll.Timeframes(OBJ_ALL_PERIODS);
//--- Установить и запомнить размер
   m_bg.Y_Size(m_caption_height);
   CElement::YSize(m_caption_height);
//--- Отключить кнопку
   m_button_unroll.MouseFocus(false);
   m_button_unroll.State(false);
//--- Состояние формы "Свернуто"
   m_is_minimized=true;
//--- Если это индикатор в подокне с фиксированной высотой и с режимом сворачивания подокна,
//    установим размер для подокна индикатора
   if(m_height_subwindow_mode)
      if(m_rollup_subwindow_mode)
         ChangeSubwindowHeight(m_caption_height+3);
  }
//+------------------------------------------------------------------+
//| Разворачивает окно                                               |
//+------------------------------------------------------------------+
void CWindow::Unroll(void)
  {
//--- Заменить кнопку
   m_button_unroll.Timeframes(OBJ_NO_PERIODS);
   m_button_rollup.Timeframes(OBJ_ALL_PERIODS);
//--- Установить и запомнить размер
   m_bg.Y_Size(m_bg_full_height);
   CElement::YSize(m_bg_full_height);
//--- Отключить кнопку
   m_button_rollup.MouseFocus(false);
   m_button_rollup.State(false);
//--- Состояние формы "Развернуто"
   m_is_minimized=false;
//--- Если это индикатор в подокне с фиксированной высотой и с режимом сворачивания подокна,
//    установим размер для подокна индикатора
   if(m_height_subwindow_mode)
      if(m_rollup_subwindow_mode)
         ChangeSubwindowHeight(m_subwindow_height);
  }

Теперь нужно создать еще один метод, принимающий строковый параметр событий графика, по которому будет производиться проверка на имя нажатого объекта, точно так же, как это было сделано в методе CWindow::CloseWindow(). И в зависимости от того, какая кнопка была нажата, будет вызываться соответствующий метод из рассмотренных в предыдущем листинге. Назовем этот метод CWindow::ChangeWindowState(). Добавьте его объявление, реализацию и вызов в обработчике событий графика в классе CWindow, как это показано в листинге кода ниже:

class CWindow: public CElement
  {
public:
   //--- Изменение состояния окна
   bool              ChangeWindowState(const string pressed_object);
  };
//+------------------------------------------------------------------+
//| Обработчик событий графика                                       |
//+------------------------------------------------------------------+
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Обработка события нажатия на объекте
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Свернуть/Развернуть окно
      ChangeWindowState(sparam);
      return;
     }
  }
//+------------------------------------------------------------------+
//| Проверка на события сворачивания/разворачивания окна             |
//+------------------------------------------------------------------+
bool CWindow::ChangeWindowState(const string pressed_object)
  {
//--- Если была нажата кнопка "Свернуть окно"
   if(pressed_object==m_button_rollup.Name())
     {
      RollUp();
      return(true);
     }
//--- Если была нажата кнопка "Развернуть окно"
   if(pressed_object==m_button_unroll.Name())
     {
      Unroll();
      return(true);
     }
//---
   return(false);
  }

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

Рис. 3. Тест функционала формы.

Рис. 3. Тест функционала формы

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

 

Удаление элементов интерфейса

Если вы дошли до этого момента и делали все в той же последовательности, как изложено в статье, то вы могли заметить, что при удалении эксперта с графика все объекты графического интерфейса удаляются. Но мы не рассматривали до сих пор методы для удаления графических объектов с графика. Почему объекты удаляются при удалении эксперта? Этот момент учтен в стандартной библиотеке классов. А именно, в деструкторе класса CChartObject, производные классы которого используются в нашей библиотеке. При удалении программы с графика вызываются деструкторы классов, включая и этот. И если объект был закреплен за этим графиком, то он удаляется (смотрите код ниже):

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CChartObject::~CChartObject(void)
  {
   if(m_chart_id!=-1)
      ::ObjectDelete(m_chart_id,m_name);
  }

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

Рис. 4. Тест формы при переключении символа и таймфрейма графика.

Рис. 4. Тест формы при переключении символа и таймфрейма графика

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

В классе CElement объявлен виртуальный метод Delete(). В каждом производном классе от CElement будет своя реализация этого метода. Ранее в классе CWindow метод Delete() уже был объявлен и сейчас нужно реализовать его (см. код ниже). В этом методе будут обнуляться некоторые переменные, производиться удаление всех объектов элемента, а также освобождаться массив указателей на объекты в базовом классе.

//+------------------------------------------------------------------+
//| Удаление                                                         |
//+------------------------------------------------------------------+
void CWindow::Delete(void)
  {
//--- Обнуление переменных
   m_right_limit=0;
//--- Удаление объектов
   m_bg.Delete();
   m_caption_bg.Delete();
   m_icon.Delete();
   m_label.Delete();
   m_button_close.Delete();
   m_button_rollup.Delete();
   m_button_unroll.Delete();
   m_button_tooltip.Delete();
//--- Освобождение массива объектов
   CElement::FreeObjectsArray();
//--- Обнуление фокуса элемента
   CElement::MouseFocus(false);
  }

Получить доступ к методам Delete() всех элементов интерфейса можно из класса CWndEvents. Поэтому далее создадим метод CWndEvents::Destroy() для удаления графического интерфейса. В нем последовательно нужно пройтись по элементам всех форм, вызывая метод Delete() каждого элемента. Перед обращением к методу Delete() нужно проверить указатель на валидность. После удаления объектов нужно освободить массивы элементов. После выхода из цикла форм нужно освободить также и их массивы.

В листинге кода ниже объявление и реализация метода CWndEvents::Destroy():

class CWndEvents : public CWndContainer
  {
protected:
   //--- Удаление интерфейса
   void              Destroy(void);
  };
//+------------------------------------------------------------------+
//| Удаление всех объектов                                           |
//+------------------------------------------------------------------+
void CWndEvents::Destroy(void)
  {
   int window_total=CWndContainer::WindowsTotal();
   for(int w=0; w<window_total; w++)
     {
      int elements_total=CWndContainer::ElementsTotal(w);
      for(int e=0; e<elements_total; e++)
        {
         //--- Если указатель невалидный, перейти к следующему
         if(::CheckPointer(m_wnd[w].m_elements[e])==POINTER_INVALID)
            continue;
         //--- Удалить объекты элемента
         m_wnd[w].m_elements[e].Delete();
        }
      //--- Освободить массивы элементов
      ::ArrayFree(m_wnd[w].m_objects);
      ::ArrayFree(m_wnd[w].m_elements);
     }
//--- Освободить массивы форм
   ::ArrayFree(m_wnd);
   ::ArrayFree(m_windows);
  }

Теперь в методе CProgram::OnDeinitEvent(), который связан с функцией OnDeinit() в главном файле программы, можно вызывать метод Destroy(). Добавьте его туда, как показано ниже:

//+------------------------------------------------------------------+
//| Деинициализация                                                  |
//+------------------------------------------------------------------+
void CProgram::OnDeinitEvent(const int reason)
  {
   //--- Удаление интерфейса
   CWndEvents::Destroy();  
  }

Скомпилируйте все файлы библиотеки, где производились последние изменения, и главный файл программы. Загрузите эксперта на график и несколько раз измените символ и таймфрейм графика. Теперь все должно работать правильно. Клоны объектов больше не появляются. Проблема решена.

 

Заключение

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

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

Список статей (глав) первой части:

Прикрепленные файлы |
Универсальный торговый эксперт: Событийная модель и прототип торговой стратегии (Часть 2) Универсальный торговый эксперт: Событийная модель и прототип торговой стратегии (Часть 2)
Данная статья продолжает серию заметок, посвященных универсальной модели эксперта. В этой части описывается оригинальная событийная модель на основе централизованной обработки данных, а также рассматривается структура базового класса движка — CStrategy.
Универсальный торговый эксперт: Торговые режимы стратегий (Часть 1) Универсальный торговый эксперт: Торговые режимы стратегий (Часть 1)
Каждый экспертописатель, независимо от уровня своей подготовки, ежедневно сталкивается с одними и теми же торговыми задачами и алгоритмическими проблемами, которые так или иначе приходится решать для организации надежного торгового процесса. Данная статья описывает возможности торгового движка CStrategy, способного взять на себя решение этих задач и предоставить пользователю удобные механизмы для описания своей торговой идеи.
Универсальный торговый эксперт: Пользовательские стратегии и вспомогательные торговые классы (Часть 3) Универсальный торговый эксперт: Пользовательские стратегии и вспомогательные торговые классы (Часть 3)
В этой статье мы продолжим описание алгоритмов торгового движка CStrategy. В третьей части серии статей подробно разобраны примеры написания конкретных торговых стратегий с использованием данного подхода. Также большое внимание уделено вспомогательным алгоритмам — системе логирования эксперта и доступу к биржевым данным с помощью обычного индексатора (Close[1], Open[0] и т.п.).
Графические интерфейсы I: "Оживление" графического интерфейса (Глава 3) Графические интерфейсы I: "Оживление" графического интерфейса (Глава 3)
В предыдущей статье серии был начат процесс разработки класса формы для элементов управления. В этой статье продолжим развивать класс, наполняя его методами для перемещения формы в области графика, а также интегрируем этот элемент интерфейса в ядро библиотеки. Кроме этого, настроим всё таким образом, чтобы при наведении курсора на элементы формы изменялся их цвет.