English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Пользовательские графические элементы управления. Часть 3. Формы

Пользовательские графические элементы управления. Часть 3. Формы

MetaTrader 5Примеры | 21 августа 2011, 19:35
7 361 23
Dmitry Fedoseev
Dmitry Fedoseev

 

Введение

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

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

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

Классы для работы с формами добавлены в файл IncGUI.mqh, применявшийся в предыдущих статьях (теперь файл имеет имя IncGUI_v3.mqh). Кроме классов для работы с формами в файл добавлено еще несколько классов, также доработаны классы CHMenu (горизонтальное меню) и CVMenu (вертикальное меню) и исправлено несколько ошибок.

Добавлены классы:

  • Класс CFrame - рамка. Класс идентичен рамке, создаваемой методом Frame класса CWorkPiece.
  • Класс CButton - кнопка. Обычная кнопка (OBJ_BUTTON).
  • Класс CLabel - надпись. Кроме отображения надписи, как это делается графическим объектом "надпись" (OBJ_LABEL) этот класс позволяет отображать текст несколькими строками, строки разделяются символом "\n".

В связи с добавлением новых классов внесены изменения в класс CColorSchemes: добавлены цвета для новых элементов управления.

Изменения в классах CHMenu и CVMenu: сделанные изменения позволяют создавать двухуровневые меню с выпадающими вкладками. Более подробно рассмотрено ниже в этой статье в разделе "Создание главного меню".

Ошибки: исправление в классе CWorkPiece в методе Frame()  - для прямоугольной метки не устанавливался номер подокна. В классе CListMS в методах Selected() и SetSelected() сделана проверка размера массива, иначе было невозможно использовать пустой список.

 

1. Формы

Форма создана на основе графических объектов "прямоугольная метка" OBJ_RECTANGLE_LABEL с использованием нескольких кнопок OBJ_BUTTON. Визуально форма представляет собой прямоугольник (рис. 1) с полосой в верхней части формы, на полосе находятся название формы и кнопки управления.

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

Рис. 1. Форма

Рис. 1. Форма

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

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

Возможно создание нескольких типов форм:

  • Тип 0 (рис. 1);
  • Тип 1 - с дополнительными кнопками "Cancel" и "Apply" (рис. 2);
  • Тип 2 - с дополнительной кнопкой "Close" (рис. 3);

Рис. 2. Форма типа 1 (с кнопками "Cancel" и "Apply")

Рис. 2. Форма типа 1 (с кнопками "Cancel" и "Apply")

Рис. 3. Форма типа 2 (с кнопкой "Close")

Рис. 3. Форма типа 2 (с кнопкой "Close")

Программные средства для работы с формами представляют собой два класса: CFormBase и CFormTemplate.

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

В классе CFormBase имеется метод Init(), в котором выполняется подготовка элемента управления, методы SetPos() и Show() для отображения формы (создания графических объектов, представляющих собой форму), метод Hide() для скрытия, Refresh() для обновления отображения, Event() для обработки событий и прочие методы, как у всех элементов управления.

Применение формы, так же как и применение элементов управления, начинается с вызова метода Init(), затем устанавливается позиция методом SetPos() и включается отображение методом Show(), или же для включения отображения используется только метод Show() с параметрами, методом Hide() выполняется скрытие формы.

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

Если обычные элементы управления являются самостоятельными элементами (они не обязаны быть связанными с другими элементами управления), то форма подразумевает собой обязательную связь с другими элементами управления для обеспечения одновременного отображения/скрытия расположенных на ней элементов управления вместе со скрытием/отображением формы. Таким образом, должна обеспечиваться синхронная работа методов Init(), Show(), Hide() формы и относящихся к ней элементов управления.

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

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

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

Для синхронизации вызова методов Init(), Show() и Hide() элементов управления и формы, у класса CFormBase есть небольшие отличия от класса обычного элемента управления: несколько виртуальных методов в секции protected:

virtual void MainProperties() {}
virtual void OnInitEvent() {}
virtual void OnShowEvent() {}
virtual void OnHideEvent() {}
virtual void EventsHandler(const int id, const long& lparam, const double& dparam, const string& sparam){}
virtual void OnWindowChangeEvent(int aSubWindow) {}
virtual bool OnCancelEvent() {return(true);}
virtual bool OnApplyEvent()  {return(true);} 

Методы виртуальные, значит через них происходит перенаправление к соответствующим реальным методам подкласса CFormTemplate.

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

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

Ниже на диаграмме показано взаимодействие функций эксперта с методами класса обычного элемента управления (рис. 4) и для сравнения взаимодействие с методами классов формы (рис. 5).

Рис. 4. Взаимодействие функций эксперта с методами элемента управления
Рис. 4. Взаимодействие функций эксперта с методами элемента управления

Рис. 5. Взаимодействие функций эксперта с методами классов формы
Рис. 5. Взаимодействие функций эксперта с методами классов формы

Разберем подробнее назначение всех методов :

  • Методы MainProperties() и OnInitEvent() вызываются из метода Init() формы. В методе MainProperties() выполняется настройка внешнего вида формы (тип формы, наличие кнопок перемещения, сворачивания, закрытия). В методе OnInitEvent() выполняется вызов методов Init() всех элементов управления и установка элементам управления значений по умолчанию.

  • Метод OnShowEvent() вызывается из метода Show(), в него добавляется код вызова методов Show() всех элементов управления.

  • Метод OnHideEvent() вызывается из метода Hide(), в него добавляется код вызова методов Hide() всех элементов управления.

  • Метод EventsHandler() является полным соответствием функции OnChartEvent() эксперта. Из функции OnChartEvent() эксперта вызывается метод Event() формы и все события графика просто перенаправляются в этот метод. В EventsHandler() добавляется код вызова методов Event() всех элементов управления и обработка их событий.

  • Метод OnWindowChangeEvent() вызывается из метода SetSubWindow() при смене подокна. В него добавляется код вызова методов SetSubWindow() всех элементов управления. У метода есть один аргумент, в нем передается новый номер подокна, который нужно установить всем элементам управления.

  • Метод OnCancelEvent() выполняется при закрытии формы (при нажатии на кнопку с крестиком, кнопку "Cancel" или кнопку "Close"). В этот метод добавляется какой-нибудь код проверки при закрытии формы, например вызов окна подтверждения, действительно ли нужно закрыть форму. Если метод будет возвращать значение true, значит, код закрывающий форму, будет отработан полностью и форма закроется, если false - выполнение кода, закрывающего форму прервется и форма останется открытой.

  • Метод OnApplyEvent() выполняется при нажатии на кнопку "Apply" формы. Здесь (как и в методе OnCancelEvent()) можно выполнить какую-нибудь проверку и вернуть из метода значение true или false и, таким образом, разрешить или запретить закрытие формы. Также в этом методе может располагаться код сохранения значений элементов управления, чтобы при следующем запуске эксперта у элементов управления были те же значения, как перед закрытием программы. Для этого в методе OnInitEvent() необходимо выполнить загрузку сохраненных значений и установку их элементам управления.

 

2. Пошаговая процедура создания новой формы

Имеем следующую пошаговую процедуру создания новой формы:

1. Присоединить файл IncGUI_v3.mqh.

#include <IncGUI_v3.mqh>
2. Скопировать код класса CFormTemplate из файла IncGUI_v3.mqh в файл своего эксперта (подкласс находится в конце файла) и переименовать его (в примере использования библиотеки подкласс имеет имя CForm). 

 

class CFormTemplate: public CFormBase{
   public:
   protected      void MainProperties()
     {
        m_Name        = "Form";       // Имя формы. С него надо начинать имена всех элементов управления этой формы.
        m_Width       = 200;           // Ширина формы
        m_Height      = 150;           // Высота формы
        m_Type        = 1;             // Тип формы: 0 - без кнопок, 1 - с кнопками "Apply" и "Cancel", 2 - с кнопкой "Close"
        m_Caption     = "FormCaption"; // Заголовок формы
        m_Movable     = true;          // Перемещаемая форма (в левом верхнем углу отображается кнопка с ладонью)
        m_Resizable   =  true;         // Разрешается сворачивание/разворачивание формы (в правом верхнем углу отображается 
                                           // кнопка с прямоугольником)
        m_CloseButton = true;          // Разрешается закрывать форму (в правом верхнем углу отображается кнопка с крестиком)
     }
      void OnInitEvent() {} 
      void OnShowEvent(int aLeft, int aTop) {}
      void OnHideEvent() {}
      void OnWindowChangeEvent(int aSubWindow) {}
      void EventsHandler(const int id,const long& lparam,const double& dparam,const string& sparam){}
      bool OnApplyEvent()  {return(true);}
      bool OnCancelEvent() {return(true);}
};

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

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

3. Установить основные свойства формы. Выполняется в методе MainProperties() путем присваивания значения переменным, объявленным в базовом классе. Назначение всех переменных подробно прокомментировано в шаблоне. Можно заметить аналогию этого шага с использованием окна свойств формы в каком-нибудь визуальном редакторе (например VBA в Excel).

void MainProperties()
{
   m_Name         =  "Form";        // Имя формы. С него надо начинать имена всех элементов управления этой формы.
   m_Width        =  200;           // Ширина формы
   m_Height       =  150;           // Высота формы
   m_Type         =  1;             // Тип формы: 0 - без кнопок, 1 - с кнопками "Apply" и "Cancel", 2 - с кнопкой "Close"
   m_Caption      =  "FormCaption";   // Заголовок формы
   m_Movable      =  true;          // Перемещаемая форма (в левом верхнем углу отображается кнопка с ладонью)
   m_Resizable    =  true;          // Разрешается сворачивание/разворачивание формы (в правом верхнем углу отображается 
                                    // кнопка с прямоугольником)
   m_CloseButton = true;            // Разрешается закрывать форму (в правом верхнем углу отображается кнопка с крестиком)
}

Можно заметить еще одно отличие формы от остальных элементов управления - имя формы задается в методе MainProperties(), а не передается как параметр метода Init().

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

Единственный параметр, который может передаваться в метод Init() - это параметр State, но и он не является обязательным. Значение параметра State определяет начальное состояние формы при отображении: развернутая или свернутая. При значения 1 форма развернута (рис. 1, 2, 3), при значении 2 - свернута (рис. 6).

Рис. 6. Свернутая форма

Рис. 6. Свернутая форма

4. Объявить класс.

CForm frm;

5. Выполнить стандартные шаги использования элемента управления: из функции OnInit() эксперта вызывать метод Init() формы, из функции OnDeinit() вызвать метод Deinit(), из функции OnChartEvent() вызывать метод Event() и при необходимости вызвать метод SetSubWindow() при событии CHARTEVENT_CHART_CHANGE.

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

Далее приступаем к добавлению элементов управления на форму.

 

3. Пошаговая процедура добавления элементов управления на форму

  1. Объявление элементов управления. Если необходимо обеспечить доступ к методам элементов управления вне пределов формы, объявление классов элементов управления выполняется в секции public, если всю работу можно выполнить в пределах класса формы, то объявление производится в секции protected.

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

  3. Вызов методов Int() всех элементов управления. Выполняется в методе OnInitEvent().

  4. Загрузка сохраненных данных. Необязательный шаг. Выполняется в методе OnInitEvent(). Используется в случае, когда необходимо, чтобы значения элементов управления сохраняли свои значения после перезапуска эксперта, терминала, компьютера. Сохранение данных выполняется на шаге 12.

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

  6. Вызов методов Show() (вариант с параметрами) для всех элементов управления выполняется в методе OnShowEvent(). У метода OnShowEvent() два параметра (int aLeft, int aTop), в которых передаются координаты левого верхнего угла рабочего пространства формы. Позиции элементов управления устанавливаются с учетом значений этих переменных (нужно прибавить значения).

  7. Вызов метода Hide() для всех элементов управления формы выполняется в методе OnHideEvent().

  8. Вызов метода SetSubWindow(). Необязательный шаг. При необходимости отображать форму в подокне, для всех элементов управления выполняется вызов метода SetSubWindow(). Выполняется в методе OnWindowChangeEvent(), у метода есть параметр (int aSubWindow), в котором передается новый номер подокна.

  9. Вызов методов Event() всех элементов управления. Выполняется в методе EventsHandler().

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

  11. Применение новых значений. Необязательный шаг, выполняется при использовании кнопки "Apply". Выполняется в методе OnApplyEvent(). На этом шаге выполняется проверка значений элементов управления на правильность, если найдены неправильные значения, необходимо выполнить уведомление пользователя и вернуть из метода значение false, при этом форма останется открытой. Если значения правильные, необходимо вернуть true, чтобы форма закрылась.

  12. Сохранение данных. Необязательный шаг. Выполняется при использовании кнопки "Apply" в методе OnApplyEvent(). Данные можно сохранять (в зависимости от целей и задач) в файле, глобальных переменных, невидимых объектах на графике и т.п.

  13. Проверка в методе OnCancelEvent(). Необязательный шаг. Если из метода OnCancelEvent() вернуть значение false, форма останется открытой, т.е. кнопка закрытия не сработает. Для того, чтобы форма закрылась, из метода надо вернуть значение true.

Пример работы с формами находится в файле eIncGUI_v3_Test_Form.mq5 приложения. На запуске открывается главная форма с различными элементами управления (рис. 7).

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

Рис. 7. Главная форма из примера eIncGUI_v3_Test_Form.mq5 с открытой вкладкой главного меню

Рис. 7. Главная форма из примера eIncGUI_v3_Test_Form.mq5 с открытой вкладкой главного меню

Через команды главного меню можно открыть два других варианта формы.

Командой Главное меню - Типы форм - Тип 1 открывается форма типа 1 (с кнопками "Cancel" и "Apply").
Командой Главное меню - Типы форм - Тип 2 открывается форма типа 2 (с кнопкой "Close").

У обеих форм требуется подтверждение при их закрытии (сделано с использованием функции MessageBox()). На форме типа 1 находится поле ввода, при нажатии кнопки "Apply" выполняется проверка введенного значения. На форме типа 2 расположен новый элемент управления "надпись" (класс CLabel) и несколько кнопок (класс CButton) для его тестирования.

Теперь, как упоминалось во введении, рассмотрим изменения, произведенные в классах CHMenu и CVMenu на примере создания главного меню.

 

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

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

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

Предварительно рассмотрим все изменения и исправления, выполненные в классах CHMenu и CVMenu.

У класса CVMenu исправлена реакция на щелчок по графическому объекту "надпись", использующемуся для отображения значка "галка". Соответственно, произошли изменения в работе методов LastClickedX(), LastClickedY(), LastClickedQuarter(). Теперь при щелчке на надписи возвращаются значения, как и для текстового поля. Аналогичные исправления произошли в классе CHMenu и методах LastClickedX(), LastClickedY(), LastClickedQuarter(), LastClickedW().

В оба класса добавлены методы SolvePosX() и SolvePosY(), эти методы предназначены для расчета координат графического объекта, отображаемого по команде меню. В метод SolvePosX() передается ширина отображаемого объекта, в SolvePosY() передается высота отображаемого объекта, соответственно, возвращается координата X или Y отображаемого объекта. Теперь нет необходимости использовать методы LastClickedX(), LastClickedY(), LastClickedQuarter(), LastClickedW().

В оба класса добавлены методы LastClickedName1() и LastClickedName2(). Методы возвращают имена графических объектов, составляющих пункт меню, на котором произошло последнее событие щелчка мышкой. LastClickedName1() возвращает имя текстового поля, LastClickedName2() возвращает имя надписи для значка "галка".

В CVMenu добавлены методы ToggleNameAdd() и ToggleNamesClear(). Метод ToggleNameAdd() используется для создания списка "переключающих" имен, ToggleNamesClear() для очистки этого списка. При использование метода ToggleNameAdd() (добавлении имени в список) меню будет автоматически закрываться при любом событии графика, кроме событий графических объектов, составляющих само меню и графических объектов, имена которых добавлены методом ToggleNameAdd(). Очистка списка методом ToggleNamesClear() возвращает меню в обычный режим работы.

Двухуровневое меню работает следующим образом: при определении события щелчка на пункте горизонтального меню методами LastClickedName1() и LastClickedName2() получаем имена объектов этого пункта, которые передаем в ToggleNameAdd() вертикального меню.

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

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

Рассмотрим создание меню на примере.

У главного меню три пункта, соответственно нужно три вертикальных меню. В секции public подкласса формы объявляем класс горизонтального меню и три класса вертикального меню (в примере ниже все показано точно так же, как сделано в примере eIncGUI_v3_Test_Form.mq5):

class CForm: public CFormBase{
public:
   CHMenu m_hm;
   CVMenu m_vm1;
   CVMenu m_vm2;
   CVMenu m_vm3; 

В методе OnInitEvent() выполняем инициализацию классов меню и добавляем в меню пункты:

// Горизонтальное меню
m_hm.Init(m_Name+"_HM",m_Width,2);
// Добавление пунктов горизонтального меню
m_hm.AddItem("Типы форм");
m_hm.AddItem("Item-2");
m_hm.AddItem("Item-3");
// Вертикальное меню 1
m_vm1.Init(m_Name+"_VM1",70,10); 
// Добавление пунктов вертикального меню 1
m_vm1.AddItem("Тип-1");
m_vm1.AddItem("Тип-2");
// Вертикальное меню 2
m_vm2.Init(m_Name+"_VM2",70,3);
// Добавление пунктов вертикального меню 2
m_vm2.AddItem("Item-2-1");
m_vm2.AddItem("Item-2-2");
m_vm2.AddItem("Item-2-3"); 
m_vm2.AddItem("Item-2-4");
m_vm2.AddItem("Item-2-5"); 
// Вертикальное меню 3
m_vm3.Init(m_Name+"_VM3",70,3);
// Добавление пунктов вертикального меню 3
m_vm3.AddItem("Item-3-1");
m_vm3.AddItem("Item-3-2");
m_vm3.AddItem("Item-3-3"); 
m_vm3.AddItem("Item-3-4");
m_vm3.AddItem("Item-3-5"); 

В методе OnHideEvent() скрываем меню:

void OnHideEvent()
{
   m_hm.Hide(); 
   m_vm1.Hide(); 
   m_vm2.Hide(); 
   m_vm3.Hide(); 
}

В методе OnShowEvent() отображаем горизонтальное меню:

void OnShowEvent(int aLeft,int aTop)
{
    m_hm.Show(aLeft,aTop); 
}

Наконец, основная работа в методе EventsHandler().

Вызываем методы Event() всех меню:

void EventsHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
{
    int m_event0=m_hm.Event(id,lparam,dparam,sparam);
    int m_event1=m_vm1.Event(id,lparam,dparam,sparam);
    int m_event2=m_vm2.Event(id,lparam,dparam,sparam);
    int m_event3=m_vm3.Event(id,lparam,dparam,sparam); 

В зависимости от значения m_event0 (индекса пункта горизонтального меню) работаем с соответствующим вертикальным меню (например, с пунктом 0 и вертикальным меню 1):

if(m_event0==0)
{ // Щелчок на пункте 0
   if(m_vm1.Visible())
   { 
      m_vm1.Hide(); // Если вертикальное меню открыто, то закрываем его
   }
   else{ // Если вертикальное меню закрыто
      m_vm1.ToggleNamesClear(); // Очищаем список "переключающих" имен
      // Добавляем в список имена графических объектов составляющих пункт 
      // горизонтального меню, от которого произошло последнее событие 
      // горизонтального меню 
      m_vm1.ToggleNameAdd(m_hm.LastClickedName1()); 
      m_vm1.ToggleNameAdd(m_hm.LastClickedName2());
      // Отображаем вертикальное меню
      m_vm1.Show(m_hm.SolvePosLeft(m_vm1.Width()),m_hm.SolvePosTop(m_vm1.Height())); 
   }
}

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

В зависимости от индекса пункта, на котором произошел щелчок мышкой, выполняем соответствующие действия:

if(m_event1>=0)
{ // Событие вертикального меню 1
   m_vm1.Hide(); // Скрываем меню
      if(m_event1==0)
      { // Щелчок на пункте 0
        //...
      }
      if(m_event1==1)
      { // Щелчок на пункте 1
        //...
      }
}

Подобные действия необходимо выполнить для всех вертикальных меню и для всех их пунктов.

На этом создание главного меню закончено.

 

Заключение

Эта статья является завершающей в серии статей, посвященных созданию графического интерфейса.

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

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

 

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

Повторим процедуру создания формы.

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

  1. Подключить файл IncGUI_v3.mqh.

  2. Сделать копию класса CFormTemplate с уникальным именем, объявить этот класс.

  3. Настроить свойства формы в методе MainProperties() копии подкласса.

  4. Выполнить вызов методов Init(), Hide(), Event() формы из функций OnInit(), OnDeinit(), OnChartEvent() эксперта. Выполнить вызов метода Show() из функции OnInit() эксперта или по мере необходимости.

  5. Выполнить работу с элементами управления. В копии подкласса объявить классы элементов управления. Вызвать метод Init(), Show(), Hide(), Event() элементов управления из методов OnInitEvent(), OnShowEvent(), OnHideEvent(), EventHandler() копии подкласса.

 

Приложение

Список файлов приложения:

  • IncGUI_v3.mqh - подключаемый файл со всеми классами для создания графического интерфейса. Файл должен располагаться в каталоге MQL5/Include каталога данных терминала.
  • eIncGUI_v3_Test_Form.mq5 - пример работы с формами. Файл должен располагаться в каталоге MQL5/Experts каталога данных терминала.
  • IncGUIv3mqh.chm - документация к файлу IncGUI_v3.mqh.
nbsp; m_vm3.Hide(); }
Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (23)
Mykola Demko
Mykola Demko | 24 авг. 2011 в 04:17
Integer:

 Господин sergeev, ... , предлагает нечто типа объединения в одном массиве переменных типа bool, int, double, string и т.д.

... 

Такое в принципе возможно, НО

такая универсализация приводит к перерасходу ресурсов в конечной реализации. Графика и так тяжела по ресурсам.

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

Но то, что, чем сложнее реализация, тем большее поле для багов это факт.

TheXpert
TheXpert | 24 авг. 2011 в 11:29
Urain:

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

Имхо, ты неправ. Дмитрий тоже неправ, Алекс тоже :) . (Все неправы! )))) )

Опять же имхо, Дмитрий выбрал оптимальный вариант по трудозатратам написания\использования.

Написать что-то более простое в использовании (не в понимании!) будет гораздо сложнее.

---
--- | 24 авг. 2011 в 13:16
TheXpert:

Написать что-то более простое в использовании (не в понимании!) будет гораздо сложнее.

а все потому что подходы разные.

Дмитрий сделал приоритет на объектах.

Алекс сделал приоритет на управлении этими объектами. И уменьшении трудозатрат на создание новых.

Два разнополюсных подхода. Здесь нельзя быть религиозно настроенным и отрицать противополжную точку зрения.

TheXpert
TheXpert | 24 авг. 2011 в 13:25
sergeev:

Два разнополюсных подхода.

Так они еще и полярные. А я вообще про экватор. Короче все правы. Смысла дискуссии без второй реализации вообще не вижу. Когда появится, продолжим :)

Dmitry Fedoseev
Dmitry Fedoseev | 29 окт. 2016 в 12:07
o_O:

...

А знаете ли, товарищ Сергеев, мог ли бы по существу подсказать, по дело. Делов то, все классы сделать членами одного класса, что бы прикреплять их к форме, в форме массив с объектов сделать. Хотя удобство от этого под вопросом. Но нет же - надо обязательно встать в позу и демонстративно нос отворотить. 

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

Да и вся теория и практика и справочное руководство в трех статьях. 

Андрей Войтенко (avoitenko): "Разработчики что-то имеют от попавших к ним в разработку идей? Абсурд!" Андрей Войтенко (avoitenko): "Разработчики что-то имеют от попавших к ним в разработку идей? Абсурд!"
Разработчик с Украины Андрей Войтенко (avoitenko) активно участвует в сервисе "Работа" на сайте mql5.com, помогая трейдерам со всего мира в реализации их идей. В прошлом году эксперт Андрея на Чемпионате Automated Trading Championship 2010 занял 4-е место, лишь немного уступив бронзовому призеру. На этот раз мы поговорим с Андреем о сервисе "Работа".
Использование прямого и обратного преобразований Фишера для анализа рынков в  MetaTrader 5 Использование прямого и обратного преобразований Фишера для анализа рынков в MetaTrader 5
Функция распределения рыночных данных не является гауссовой, скорее она похожа на распределение синусоподобной волны. Поскольку большинство индикаторов базируются на предположении о нормальном распределении цен, их нужно "скорректировать". Решением является использование преобразования Фишера, которое преобразует данные таким образом, чтобы они имели распределение, близкое к нормальному. В статье рассмотрена теория прямого и обратного преобразования Фишера и ее применение в трейдинге, разработан модуль торговых сигналов.
Андрей Болконский (abolk): "Любой программист знает - программного продукта без багов не бывает" Андрей Болконский (abolk): "Любой программист знает - программного продукта без багов не бывает"
В сервисе "Работа" Андрей Болконский (abolk) участвует с момента его открытия. На текущий момент на его счету десятки индикаторов и экспертов для платформ MetaTrader 4 и MetaTrader 5. С Андреем мы поговорим о том, что представляет собой сервис с точки зрения программиста.
Пользовательские графические элементы управления. Часть 2. Библиотека элементов управления Пользовательские графические элементы управления. Часть 2. Библиотека элементов управления
Во второй статье серии "Пользовательские графические элементы управления" представлена библиотека элементов управления для решения основных задач, возникающих при обеспечении взаимодействия между программой (советником, скриптом, индикатором) и ее пользователем. Библиотека содержит множество классов (CInputBox, CSpinInputBox, CCheckBox, CRadioGroup, CVSсrollBar, CHSсrollBar, CList, CListMS, CComBox, CHMenu, CVMenu, CHProgress, CDialer, CDialerInputBox, CTable) и примеров их использования.