Торговый эксперт с графическим интерфейсом: Создание панели (Часть I)
Содержание
- Введение
- Элементы графического интерфейса
- Сборка графического интерфейса
- Форма для элементов управления
- Статусная строка
- Группа вкладок
- Поле ввода
- Кнопка
- Комбо-бокс с выпадающим списком
- Чек-бокс
- Таблица
- Стандартный график
- Индикатор выполнения
- Обновления библиотеки EasyAndFast
- Заключение
Введение
Несмотря на активное развитие алготрейдинга, многие трейдеры до сих пор предпочитают ручную торговлю. Но и здесь обойтись абсолютно без автоматизации рутинных операций вряд ли получится.
В этой статье продемонстрировано создание мультисимвольного сигнального эксперта для ручной торговли. В качестве примера рассмотрим сигналы индикатора Stochastic из стандартной поставки терминала. Этот код можно использовать для создания собственных экспертов с графическим интерфейсом: в него можно подключить любой другой индикатор или использовать результаты определенных вычислений для принятия решений.
Для тех, кто выполняет работы на заказ, эта статья может быть полезна как пример технического задания для демонстрации заказчикам. Возможно, что этот пример поможет сократить время по составлению понятного для программиста технического задания на программу с графическим интерфейсом.
Перечислим вопросы, которые подробно будут рассмотрены в этой работе.
- Создание графического интерфейса.
- Получение списка символов с заданными свойствами.
- Элементы управления торговыми операциями.
- Быстрое переключение символов и таймфреймов на графиках без переинициализации эксперта.
- Управление свойствами графиков через пользовательский интерфейс.
- Получение сигналов индикатора от множества символов с цветовой индикацией.
- Работа с открытыми позициями.
- Обновления библиотеки EasyAndFast.
Статья будет опубликована в двух частях. В настоящей статье рассмотрено создание панели, в следующей части будет описано ее наполнение функционалом.
Элементы графического интерфейса
Разработку эксперта начнем с построения графического интерфейса, через который будет реализовано взаимодействие с пользователем и визуализация данных. Можно создать графический интерфейс, воспользовавшись возможностями стандартной библиотеки, но в моем примере он будет реализован на основе библиотеки EasyAndFast. Ее богатые возможности позволяют сосредоточиться на функционале самой программы, не отвлекаясь на доработку её графической части.
Сначала обрисуем общую схему графического интерфейса. На схеме ниже показано, что в окне графического интерфейса есть группа из двух вкладок. В списках отображаются функции, которые на них нужно разместить. Это упрощённый пример, и заказчик с исполнителем могут проработать его более детально в процессе обсуждения.
Рис. 1. Общая схема графического интерфейса с пояснениями.
Элементов управления графическим интерфейсом может быть очень много. Поэтому сначала просто перечислим их в иерархическом виде:
- Форма для элементов управления
- Строка состояния для показа дополнительной итоговой информации
- Группа вкладок:
- Trade:
- Поле ввода с чек-боксом для фильтрации списка символов
- Кнопка запроса для начала сбора списка символов
- Кнопка для торговой операции SELL
- Кнопка для торговой операции BUY
- Поле ввода для установки объёма сделки
- Кнопка для закрытия всех открытых позиций
- Поле ввода установки уровня для сигнала на продажу
- Поле ввода установки уровня для сигнала на покупку
- Таблица символов для торговли
- График для визуализации данных символов. Для удобства сделаем так, чтобы некоторыми свойствами графика можно было управлять с помощью следующей группы элементов:
- Комбо-бокс с выпадающим списком для выбора переключения таймфрейма
- Чек-бокс для включения/выключения временной шкалы
- Чек-бокс для включения/выключения ценовой шкалы
- Поле ввода для управления масштабом
- Кнопка для включения отступа
- Чек-бокс для показа индикатора
- Positions:
- Таблица позиций
- Индикатор выполнения процесса повторного проигрывания фреймов
В главном классе программы (CProgram) объявляем методы и экземпляры классов вышеперечисленных элементов. Код методов для создания элементов вынесен в отдельный файл и подключается к файлу с классом MQL-программы:
//+------------------------------------------------------------------+ //| Класс для создания приложения | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- Окно CWindow m_window1; //--- Статусная строка CStatusBar m_status_bar; //--- Вкладки CTabs m_tabs1; //--- Поля ввода CTextEdit m_symb_filter; CTextEdit m_lot; CTextEdit m_up_level; CTextEdit m_down_level; CTextEdit m_chart_scale; //--- Кнопки CButton m_request; CButton m_chart_shift; CButton m_buy; CButton m_sell; CButton m_close_all; //--- Комбо-боксы CComboBox m_timeframes; //--- Чек-боксы CCheckBox m_date_scale; CCheckBox m_price_scale; CCheckBox m_show_indicator; //--- Таблицы CTable m_table_positions; CTable m_table_symb; //--- Стандартный график CStandardChart m_sub_chart1; //--- Индикатор выполнения CProgressBar m_progress_bar; //--- public: //--- Создаёт графический интерфейс bool CreateGUI(void); //--- private: //--- Форма bool CreateWindow(const string text); //--- Статусная строка bool CreateStatusBar(const int x_gap,const int y_gap); //--- Вкладки bool CreateTabs1(const int x_gap,const int y_gap); //--- Поля ввода bool CreateSymbFilter(const int x_gap,const int y_gap,const string text); bool CreateLot(const int x_gap,const int y_gap,const string text); bool CreateUpLevel(const int x_gap,const int y_gap,const string text); bool CreateDownLevel(const int x_gap,const int y_gap,const string text); bool CreateChartScale(const int x_gap,const int y_gap,const string text); //--- Кнопки bool CreateRequest(const int x_gap,const int y_gap,const string text); bool CreateChartShift(const int x_gap,const int y_gap,const string text); bool CreateBuy(const int x_gap,const int y_gap,const string text); bool CreateSell(const int x_gap,const int y_gap,const string text); bool CreateCloseAll(const int x_gap,const int y_gap,const string text); //--- Комбо-бокс bool CreateComboBoxTF(const int x_gap,const int y_gap,const string text); //--- Чек-боксы bool CreateDateScale(const int x_gap,const int y_gap,const string text); bool CreatePriceScale(const int x_gap,const int y_gap,const string text); bool CreateShowIndicator(const int x_gap,const int y_gap,const string text); //--- Таблицы bool CreatePositionsTable(const int x_gap,const int y_gap); bool CreateSymbolsTable(const int x_gap,const int y_gap); //--- Стандартный график bool CreateSubChart1(const int x_gap,const int y_gap); //--- Индикатор выполнения bool CreateProgressBar(const int x_gap,const int y_gap,const string text); }; //+------------------------------------------------------------------+ //| Методы для создания элементов управления | //+------------------------------------------------------------------+ #include "CreateGUI.mqh" //+------------------------------------------------------------------+
Далее подробнее рассмотрим сборку графического интерфейса, методы для создания его элементов и их свойства.
Сборка графического интерфейса
Итак, в графическом интерфейсе разрабатываемого приложения будут использоваться элементы десяти разных типов.
- Форма для элементов управления (CWindow)
- Статусная строка (CStatusBar)
- Группа вкладок (CTabs)
- Поле ввода (CTextEdit)
- Кнопка (CButton)
- Комбо-бокс с выпадающим списком (CComboBox)
- Чек-бокс (CCheckBox)
- Таблица (CTable)
- Стандартный график (CStandardChart)
- Индикатор выполнения (CProgressBar)
Некоторых элементов из этого списка будет несколько, поэтому рассмотрим только по одному из каждой группы. В той же последовательности рассмотрим методы для их создания.
Форма для элементов управления
Ниже представлен код метода для создания формы, на которой будут располагаться все остальные элементы. В начале нужно добавить форму в список элементов графического интерфейса программы. Для этого нужно вызвать метод CWndContainer::AddWindow(), передав в него объект элемента типа CWindow. Затем, перед созданием формы, устанавливаем её свойства. Мы устанавливаем следующие свойства (в той же последовательности, что и в листинге ниже):
- Размеры формы (ширина и высота)
- Размер шрифта в заголовке
- Режим перемещения формы (в пределах графика)
- Режим изменения размеров формы вручную (перетаскиванием границ)
- Кнопки формы. Видимость каждой кнопки включается отдельно. В данном случае задействованы следующие из них:
- Закрытие формы. В главной форме программы при нажатии на эту кнопку, выйдет диалоговое окно с вопросом о закрытии программы.
- Сворачивание и развёртывание формы.
- Всплывающие подсказки элементов. Эта кнопка тоже имеет два состояния. Если она нажата, то в элементах управления будут показываться всплывающие подсказки (при условии, что они были заданы).
- Разворачивание формы на всю область графика терминала. Развернув форму, её затем можно вернуть в предыдущие размеры, нажав эту кнопку повторно.
- Для каждой кнопки формы можно установить всплывающую подсказку.
После того, как свойства заданы, нужно вызвать метод создания формы — CWindow::CreateWindow() и передать в него:
- идентификатор графика,
- номер подокна графика,
- текст заголовка,
- начальные координаты расположения для формы.
//+------------------------------------------------------------------+ //| Создаёт форму для элементов управления | //+------------------------------------------------------------------+ bool CProgram::CreateWindow(const string caption_text) { //--- Добавим указатель окна в массив окон CWndContainer::AddWindow(m_window1); //--- Свойства m_window1.XSize(750); m_window1.YSize(450); m_window1.FontSize(9); m_window1.IsMovable(true); m_window1.ResizeMode(true); m_window1.CloseButtonIsUsed(true); m_window1.CollapseButtonIsUsed(true); m_window1.TooltipsButtonIsUsed(true); m_window1.FullscreenButtonIsUsed(true); //--- Установим всплывающие подсказки m_window1.GetCloseButtonPointer().Tooltip("Close"); m_window1.GetTooltipButtonPointer().Tooltip("Tooltips"); m_window1.GetFullscreenButtonPointer().Tooltip("Fullscreen"); m_window1.GetCollapseButtonPointer().Tooltip("Collapse/Expand"); //--- Создание формы if(!m_window1.CreateWindow(m_chart_id,m_subwin,caption_text,1,1)) return(false); //--- return(true); }
Каждый раз, добавляя в код новый элемент графического интерфейса, желательно скомпилировать программу и посмотреть, какой получается результат:
Рис. 2. Форма для элементов управления.
Ниже будут показаны скриншоты всех промежуточных результатов.
Статусная строка
Код метода для создания статусной строки начинается с указания главного элемента. От него будет рассчитываться позиционирование и выравнивание по размеру привязанных к нему элементов. Это экономит время при разработке приложения: целую группу связанных элементов можно будет переместить, изменив координаты только у главного из них. Для привязки элемента его указатель передается в метод CElement::MainPointer(). В данном примере мы привязываем статусную строку к форме, поэтому в метод передаём объект формы.
Затем установим свойства статусной строки. В ней сделаем три пункта, в которых будет показываться информация для пользователя.
- Чтобы не указывать размеры относительно формы, укажем, что ширина должна изменяться автоматически при изменении ширины формы.
- Отступ правого края элемента от правого края формы будет 1 пиксель.
- Привяжем статусную строку к нижней части формы, чтобы при изменении высоты формы она автоматически корректировалась по нижему краю.
- Далее, при добавлении пунктов, указываем для каждого ширину.
После того, как свойства заданы, создаём элемент. Теперь он готов для работы, и мы можем изменять текст в его пунктах во время выполнения программы. В нашем примере установим в первом пункте текст «For Help, press F1».
В конце метода нужно обязательно сохранить указатель созданного элемента в общем списке элементов графического интерфейса. Для этого вызовем метод CWndContainer::AddToElementsArray(), передадим в него индекс формы и объект элемента. Поскольку форма у нас только одна, то ее индекс будет 0.
//+------------------------------------------------------------------+ //| Создаёт статусную строку | //+------------------------------------------------------------------+ bool CProgram::CreateStatusBar(const int x_gap,const int y_gap) { #define STATUS_LABELS_TOTAL 3 //--- Сохраним указатель на окно m_status_bar.MainPointer(m_window1); //--- Свойства m_status_bar.AutoXResizeMode(true); m_status_bar.AutoXResizeRightOffset(1); m_status_bar.AnchorBottomWindowSide(true); //--- Укажем, сколько должно быть частей и установим им свойства int width[STATUS_LABELS_TOTAL]={0,200,110}; for(int i=0; i<STATUS_LABELS_TOTAL; i++) m_status_bar.AddItem(width[i]); //--- Создадим элемент управления if(!m_status_bar.CreateStatusBar(x_gap,y_gap)) return(false); //--- Установка текста в пункты статусной строки m_status_bar.SetValue(0,"For Help, press F1"); //--- Добавим объект в общий массив групп объектов CWndContainer::AddToElementsArray(0,m_status_bar); return(true); }
Остальные элементы библиотеки EasyAndFast создаются по тому же принципу. Поэтому дальше будем рассматривать только задаваемые свойства, которые будут использоваться в нашем эксперте.
Рис. 3. Добавление статусной строки.
Группа вкладок
В методе для создания группы вкладок зададим следующие свойства элементу:
- Текст во вкладках сделаем по центру.
- Вкладки расположим в верхней части рабочей области.
- Размеры будут автоматически подстраиваться под область главного элемента (форма). В таком случае необязательно указывать размеры области группы вкладок.
- Укажем отступы от правого и нижнего края главного элемента. При изменении размеров формы эти отступы будут сохраняться.
- При добавлении следующих вкладок в метод передаются также название вкладки и её ширина.
Вот код метода:
//+------------------------------------------------------------------+ //| Создаёт группу с вкладками 1 | //+------------------------------------------------------------------+ bool CProgram::CreateTabs1(const int x_gap,const int y_gap) { #define TABS1_TOTAL 2 //--- Сохраним указатель на главный элемент m_tabs1.MainPointer(m_window1); //--- Свойства m_tabs1.IsCenterText(true); m_tabs1.PositionMode(TABS_TOP); m_tabs1.AutoXResizeMode(true); m_tabs1.AutoYResizeMode(true); m_tabs1.AutoXResizeRightOffset(3); m_tabs1.AutoYResizeBottomOffset(25); //--- Добавим вкладки с указанными свойствами string tabs_names[TABS1_TOTAL]={"Trade","Positions"}; for(int i=0; i<TABS1_TOTAL; i++) m_tabs1.AddTab(tabs_names[i],100); //--- Создадим элемент управления if(!m_tabs1.CreateTabs(x_gap,y_gap)) return(false); //--- Добавим объект в общий массив групп объектов CWndContainer::AddToElementsArray(0,m_tabs1); return(true); }
Рис. 4. Добавление группы вкладок.
Поле ввода
Для примера рассмотрим поле ввода, в котором пользователь может указать валюты и/или валютные пары для формирования списка символов в таблице. Главным элементом для него будет группа вкладок. Здесь же нужно указать, на какой вкладке нужно отображать это поле ввода. Для этого вызовем метод CTabs::AddToElementsArray(), передадим в него индекс вкладки и объект присоединяемого элемента.
Далее рассмотрим свойства, устанавливаемые для этого поля ввода.
- По умолчанию в поле ввода будет введён текст «USD»: программа будет собирать в таблицу символы, в которых есть USD. Указывать валюты и/или символы в этом поле ввода нужно через запятую. Ниже я покажу метод, с помощью которого формируется список символов по перечисленным через запятую строкам в этом поле ввода.
- Поле ввода будет с чек-боксом. При отключении чек-бокса текст в поле ввода игнорируется, и в список символов попадут все найденные валютные пары.
- Ширина поля ввода будет на всю ширину главного элемента, и при изменении ширины области вкладок будет тоже корректироваться.
- Справа от поля ввода будет кнопка Request. Во время работы программы в поле ввода можно указать другие валюты и/или символы, а для того, чтобы список сформировался, нужно будет нажать на эту кнопку. Так как подразумевается автоматическое изменение ширины поля ввода, а кнопка Request должна быть всегда справа от него, то нужно, чтобы у правой стороны поля ввода всегда был отступ от правого края главного элемента.
Элемент типа CTextEdit состоит из нескольких других элементов. Поэтому, если понадобится изменить их свойства, есть возможность получать указатели на них. Нам потребовалось изменить некоторые свойства текстового поля ввода (CTextBox). Рассмотрим их в той же последовательности, как это реализовано в листинге кода ниже.
- Отступ поля ввода от левого края главного элемента (CTextEdit).
- Автоматическое изменение ширины относительно главного элемента.
- При активации поля ввода (клик левой кнопки мыши на поле ввода) текст будет выделяться полностью автоматически для возможности быстрой замены.
- Если в поле ввода вообще не будет текста, то будет показываться такая подсказка: «Example: EURUSD, GBP, NOK».
По умолчанию чек-бокс поля ввода включен. Для этого нужно его активировать сразу после создания элемента.
//+------------------------------------------------------------------+ //| Создаёт чекбокс с полем ввода "Symbols filter" | //+------------------------------------------------------------------+ bool CProgram::CreateSymbolsFilter(const int x_gap,const int y_gap,const string text) { //--- Сохраним указатель на главный элемент m_symb_filter.MainPointer(m_tabs1); //--- Закрепить за вкладкой m_tabs1.AddToElementsArray(0,m_symb_filter); //--- Свойства m_symb_filter.SetValue("USD"); // "EUR,USD" "EURUSD,GBPUSD" "EURUSD,GBPUSD,AUDUSD,NZDUSD,USDCHF" m_symb_filter.CheckBoxMode(true); m_symb_filter.AutoXResizeMode(true); m_symb_filter.AutoXResizeRightOffset(90); m_symb_filter.GetTextBoxPointer().XGap(100); m_symb_filter.GetTextBoxPointer().AutoXResizeMode(true); m_symb_filter.GetTextBoxPointer().AutoSelectionMode(true); m_symb_filter.GetTextBoxPointer().DefaultText("Example: EURUSD,GBP,NOK"); //--- Создадим элемент управления if(!m_symb_filter.CreateTextEdit(text,x_gap,y_gap)) return(false); //--- Включим чек-бокс m_symb_filter.IsPressed(true); //--- Добавим объект в общий массив групп объектов CWndContainer::AddToElementsArray(0,m_symb_filter); return(true); }
Кроме текстового поля ввода, в графическом интерфейсе будут и числовые. Например, поле ввода Lot (объём для открытия позиций). Для полей ввода такого типа нужно указывать другие свойства.
- Общая ширина элемента.
- Максимальное значение для ввода.
- Минимальное значение для ввода.
- Шаг при переключении кнопками инкремента и декремента.
- Количество знаков после запятой.
- Для того, чтобы поле ввода стало числовым, нужно это указать с помощью метода CTextEdit::SpinEditMode().
- Значение по умолчанию после загрузки программы на график в терминале.
- Ширина поля ввода.
- Автоматическое выделение текста в поле ввода при клике на нём.
- Примагничивание поля ввода к правому краю элемента.
Вот код этого метода:
//+------------------------------------------------------------------+ //| Создаёт поле ввода "Lot" | //+------------------------------------------------------------------+ bool CProgram::CreateLot(const int x_gap,const int y_gap,const string text) { //--- Сохраним указатель на главный элемент m_lot.MainPointer(m_tabs1); //--- Закрепить за вкладкой m_tabs1.AddToElementsArray(0,m_lot); //--- Свойства m_lot.XSize(80); m_lot.MaxValue(1000); m_lot.MinValue(0.01); m_lot.StepValue(0.01); m_lot.SetDigits(2); m_lot.SpinEditMode(true); m_lot.SetValue((string)0.1); m_lot.GetTextBoxPointer().XSize(50); m_lot.GetTextBoxPointer().AutoSelectionMode(true); m_lot.GetTextBoxPointer().AnchorRightWindowSide(true); //--- Создадим элемент управления if(!m_lot.CreateTextEdit(text,x_gap,y_gap)) return(false); //--- Добавим объект в общий массив групп объектов CWndContainer::AddToElementsArray(0,m_lot); return(true); }
Рис. 5. Добавление полей ввода.
Картинка выглядит не очень логичной, но при добавлении остальных элементов всё окажется на своих местах.
Кнопка
Добавим в графический интерфейс нашего эксперта несколько кнопок. Рассмотрим ту из них, в которой задействовано больше всего свойств: кнопку для открытия позиций SELL.
- Ширина кнопки.
- Текст кнопки будет точно по центру, как по вертикали, так и по горизонтали.
- Цвет фона кнопки.
- Цвет фона при наведении курсора.
- Цвет фона при клике левой кнопкой.
- Цвет текста кнопки.
- Цвет текста при наведении курсора.
- Цвет текста при клике левой кнопкой.
- Цвет рамки кнопки.
- Цвет рамки при наведении курсора.
- Цвет рамки при клике левой кнопкой.
Такие же свойства изменяются и в кнопке BUY, с различием только в задаваемых оттенках фона.
//+------------------------------------------------------------------+ //| Создаёт кнопку 'Sell' | //+------------------------------------------------------------------+ bool CProgram::CreateSell(const int x_gap,const int y_gap,const string text) { //--- Сохранить указатель на главный элемент m_sell.MainPointer(m_tabs1); //--- Закрепить за вкладкой m_tabs1.AddToElementsArray(0,m_sell); //--- Свойства m_sell.XSize(80); m_sell.IsCenterText(true); m_sell.BackColor(C'255,51,51'); m_sell.BackColorHover(C'255,100,100'); m_sell.BackColorPressed(C'195,0,0'); m_sell.LabelColor(clrWhite); m_sell.LabelColorHover(clrWhite); m_sell.LabelColorPressed(clrWhite); m_sell.BorderColor(clrBlack); m_sell.BorderColorHover(clrBlack); m_sell.BorderColorPressed(clrBlack); //--- Создадим элемент управления if(!m_sell.CreateButton(text,x_gap,y_gap)) return(false); //--- Добавим указатель на элемент в базу CWndContainer::AddToElementsArray(0,m_sell); return(true); }
Рис. 6. Добавление кнопок.
Комбо-бокс с выпадающим списком
Для изменения таймфрейма сделаем комбо-бокс с выпадающим списком. Перечислим свойства для его настройки.
- Общая ширина элемента.
- Количество пунктов в списке (в нашем случае 21, по количеству таймфреймов в терминале).
- Элемент будет привязан к правой части области вкладок.
- Ширина кнопки комбо-бокса.
- Кнопка привязана к правой части элемента.
Далее каждому пункту в списке присваиваются значения, а после этого через указатель устанавливаются некоторые свойства списку.
- Подсветка пунктов по наведению курсора мыши.
- Выделенный пункт. В данном случае это будет пункт по индексу 18 (таймфрейм D1).
Вот код метода для создания этого комбо-бокса:
//+------------------------------------------------------------------+ //| Создаёт комбо-бокс для выбора таймфреймов | //+------------------------------------------------------------------+ bool CProgram::CreateComboBoxTF(const int x_gap,const int y_gap,const string text) { //--- Общее количество пунктов в списке #define ITEMS_TOTAL2 21 //--- Передать объект панели m_timeframes.MainPointer(m_tabs1); //--- Закрепить за вкладкой m_tabs1.AddToElementsArray(0,m_timeframes); //--- Свойства m_timeframes.XSize(115); m_timeframes.ItemsTotal(ITEMS_TOTAL2); m_timeframes.AnchorRightWindowSide(true); m_timeframes.GetButtonPointer().XSize(50); m_timeframes.GetButtonPointer().AnchorRightWindowSide(true); //--- Сохраним значения пунктов в список комбо-бокса string items_text[ITEMS_TOTAL2]={"M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30","H1","H2","H3","H4","H6","H8","H12","D1","W1","MN"}; for(int i=0; i<ITEMS_TOTAL2; i++) m_timeframes.SetValue(i,items_text[i]); //--- Получим указатель списка CListView *lv=m_timeframes.GetListViewPointer(); //--- Установим свойства списка lv.LightsHover(true); lv.SelectItem(18); //--- Создадим элемент управления if(!m_timeframes.CreateComboBox(text,x_gap,y_gap)) return(false); //--- Добавим указатель на элемент в базу CWndContainer::AddToElementsArray(0,m_timeframes); return(true); }
Рис. 7. Добавление комбо-бокса.
Чек-бокс
Чек-бокс — самый простой элемент. Для него достаточно указать два свойства.
- Ширина.
- Расположение в правой части главного элемента.
После создания элемента можно программно включить чек-бокс.
//+------------------------------------------------------------------+ //| Создаёт чекбокс "Date scale" | //+------------------------------------------------------------------+ bool CProgram::CreateDateScale(const int x_gap,const int y_gap,const string text) { //--- Сохраним указатель на окно m_date_scale.MainPointer(m_tabs1); //--- Закрепить за вкладкой m_tabs1.AddToElementsArray(0,m_date_scale); //--- Свойства m_date_scale.XSize(70); m_date_scale.AnchorRightWindowSide(true); //--- Создадим элемент управления if(!m_date_scale.CreateCheckBox(text,x_gap,y_gap)) return(false); //--- Включить чек-бокс m_date_scale.IsPressed(true); //--- Добавим объект в общий массив групп объектов CWndContainer::AddToElementsArray(0,m_date_scale); return(true); }
Рис. 8. Добавление чек-боксов.
Таблица
В графическом интерфейсе будут две таблицы. Рассмотрим ту, которая визуализирует сформированный список символов и сигналов для открытия позиций. Она размещается на первой вкладке. Сначала объявляем и инициализируем массивы для установки свойств таблицы. Настроим следующие свойства.
- Ширина элемента.
- Размерность таблицы (количество столбцов и строк).
- Ширина столбцов (значения передаются в массиве).
- Выравнивание текста (значения передаются в массиве).
- Отступ для текста от краёв ячеек.
- Показ заголовков.
- Возможность выделять строки.
- Возможность изменять размеры столбцов вручную, захватывая границу заголовка.
- Отображение с форматированием в стиле «Зебра».
- Автоматическое изменение размера по вертикали относительно главного элемента.
- Отступ от нижнего края главного элемента.
Текст для заголовков можно указать после создания таблицы:
//+------------------------------------------------------------------+ //| Создаёт таблицу символов | //+------------------------------------------------------------------+ bool CProgram::CreateSymbolsTable(const int x_gap,const int y_gap) { #define COLUMNS1_TOTAL 2 #define ROWS1_TOTAL 1 //--- Сохраним указатель на главный элемент m_table_symb.MainPointer(m_tabs1); //--- Закрепить за вкладкой m_tabs1.AddToElementsArray(0,m_table_symb); //--- Массив ширины столбцов int width[COLUMNS1_TOTAL]={95,58}; //--- Массив выравнивания текста в столбцах ENUM_ALIGN_MODE align[COLUMNS1_TOTAL]={ALIGN_LEFT,ALIGN_RIGHT}; //--- Массив отступа текста в столбцах по оси X int text_x_offset[COLUMNS1_TOTAL]={5,5}; //--- Свойства m_table_symb.XSize(168); m_table_symb.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL); m_table_symb.ColumnsWidth(width); m_table_symb.TextAlign(align); m_table_symb.TextXOffset(text_x_offset); m_table_symb.ShowHeaders(true); m_table_symb.SelectableRow(true); m_table_symb.ColumnResizeMode(true); m_table_symb.IsZebraFormatRows(clrWhiteSmoke); m_table_symb.AutoYResizeMode(true); m_table_symb.AutoYResizeBottomOffset(2); //--- Создадим элемент управления if(!m_table_symb.CreateTable(x_gap,y_gap)) return(false); //--- Установим названия заголовков m_table_symb.SetHeaderText(0,"Symbol"); m_table_symb.SetHeaderText(1,"Values"); //--- Добавим объект в общий массив групп объектов CWndContainer::AddToElementsArray(0,m_table_symb); return(true); }
Вторая таблица отображает некоторые свойства открытых позиций. Десять ее столбцов будут отображать следующие данные.
- Символ позиции.
- Количество позиций.
- Общий объём всех открытых позиций.
- Объём позиций BUY.
- Объём позиций SELL.
- Текущий общий результат по всем открытым позициям.
- Текущий результат по всем открытым BUY-позициям.
- Текущий результат по всем открытым SELL-позициям.
- Загрузка депозита по каждому символу отдельно.
- Средняя цена.
Во второй таблице дополнительно нужно настроить следующие свойства.
- Отступы для картинок от правого и верхнего краёв ячеек.
- Возможность сортировки значений.
- Автоматическое изменение размера по горизонтали относительно главного элемента.
- Отступ от правого края главного элемента.
Картинки в ячейках первого столбца будут символизировать кнопки, нажимая на которые, можно закрыть позицию или все позиции, если это хедж-счёт, на указанном символе.
//+------------------------------------------------------------------+ //| Создаёт таблицу позиций | //+------------------------------------------------------------------+ bool CProgram::CreatePositionsTable(const int x_gap,const int y_gap) { ... //--- Свойства m_table_positions.TableSize(COLUMNS2_TOTAL,ROWS2_TOTAL); m_table_positions.ColumnsWidth(width); m_table_positions.TextAlign(align); m_table_positions.TextXOffset(text_x_offset); m_table_positions.ImageXOffset(image_x_offset); m_table_positions.ImageYOffset(image_y_offset); m_table_positions.ShowHeaders(true); m_table_positions.IsSortMode(true); m_table_positions.SelectableRow(true); m_table_positions.ColumnResizeMode(true); m_table_positions.IsZebraFormatRows(clrWhiteSmoke); m_table_positions.AutoXResizeMode(true); m_table_positions.AutoYResizeMode(true); m_table_positions.AutoXResizeRightOffset(2); m_table_positions.AutoYResizeBottomOffset(2); ... return(true); }
Рис. 9. Добавление таблицы на первой вкладке.
Рис. 10. Добавление таблицы на второй вкладке.
О том, как работать с таблицами в главном файле программы (CProgram), я расскажу в одном из следующих разделах статьи.
Стандартный график
Для визуализации данных по символам предназначен элемент типа CStandardChart. По умолчанию будет показываться график EURUSD на дневном таймфрейме. В нем задействованы следующие свойства.
- Горизонтальный скроллинг.
- Автоматическая подстройка ширины.
- Автоматическая подстройка высоты.
- Отступ от правого края главного элемента.
- Отступ от нижнего края главного элемента.
При необходимости можно создать массив графиков, выстроенных в горизонтальный ряд. Для этого воспользуйтесь методом CStandardChart::AddSubChart() передав в него в качестве аргументов символ и таймфрейм графика. Но в данном случае нам нужен только один график, а символы и таймфреймы будем переключать с помощью других элементов управления.
//+------------------------------------------------------------------+ //| Создаёт стандартный график 1 | //+------------------------------------------------------------------+ bool CProgram::CreateSubChart1(const int x_gap,const int y_gap) { //--- Сохраним указатель на окно m_sub_chart1.MainPointer(m_tabs1); //--- Закрепить за 1-ой вкладкой m_tabs1.AddToElementsArray(0,m_sub_chart1); //--- Свойства m_sub_chart1.XScrollMode(true); m_sub_chart1.AutoXResizeMode(true); m_sub_chart1.AutoYResizeMode(true); m_sub_chart1.AutoXResizeRightOffset(125); m_sub_chart1.AutoYResizeBottomOffset(2); //--- Добавим графики m_sub_chart1.AddSubChart("EURUSD",PERIOD_D1); //--- Создадим элемент управления if(!m_sub_chart1.CreateStandardChart(x_gap,y_gap)) return(false); //--- Добавим объект в общий массив групп объектов CWndContainer::AddToElementsArray(0,m_sub_chart1); return(true); }
Рис. 11. Добавление графика.
Индикатор выполнения
Для того, чтобы пользователь понимал, что сейчас делает программа, добавим в графический интерфейс индикатор выполнения. Вот свойства для нашего примера (в том же порядке, что и в коде).
- Общая высота элемента.
- Высота индикатора (полоса прогресса выполнения).
- Отступ индикатора по оси X.
- Отступ индикатора по оси Y.
- Отступ главной текстовой метки по оси X.
- Отступ главной текстовой метки по оси Y.
- Отступ процентной текстовой метки по оси X.
- Отступ процентной текстовой метки по оси Y.
- Признак выпадающего элемента (для автоматического скрытия).
- Шрифт.
- Цвет рамки индикатора.
- Цвет фона индикатора.
- Цвет полосы прогресса индикатора.
- Автоматическая подстройка ширины.
- Отступ от правого края главного элемента.
Ниже я покажу примеры использования индикатора выполнения.
//+------------------------------------------------------------------+ //| Создаёт прогресс-бар | //+------------------------------------------------------------------+ bool CProgram::CreateProgressBar(const int x_gap,const int y_gap,const string text) { //--- Сохраним указатель на главный элемент m_progress_bar.MainPointer(m_status_bar); //--- Свойства m_progress_bar.YSize(17); m_progress_bar.BarYSize(14); m_progress_bar.BarXGap(0); m_progress_bar.BarYGap(1); m_progress_bar.LabelXGap(5); m_progress_bar.LabelYGap(2); m_progress_bar.PercentXGap(5); m_progress_bar.PercentYGap(2); m_progress_bar.IsDropdown(true); m_progress_bar.Font("Consolas"); m_progress_bar.BorderColor(clrSilver); m_progress_bar.IndicatorBackColor(clrWhiteSmoke); m_progress_bar.IndicatorColor(clrLightGreen); m_progress_bar.AutoXResizeMode(true); m_progress_bar.AutoXResizeRightOffset(2); //--- Создание элемента if(!m_progress_bar.CreateProgressBar(text,x_gap,y_gap)) return(false); //--- Добавим указатель на элемент в базу CWndContainer::AddToElementsArray(0,m_progress_bar); return(true); }
Мы рассмотрели все элементы управления, которые будут использоваться в графическом интерфейсе нашего эксперта. На текущий момент это просто графическая оболочка. Но дальше мы напишем все необходимые методы для того, чтобы всё это заработало в соответствии с изначальной идеей.
Обновления библиотеки EasyAndFast
В библиотеке EasyAndFast в классе CTable доработан публичный метод CTable::SortData(). Теперь в качестве второго аргумента можно указать направление сортировки таблицы (необязательный параметр). Раньше повторный вызов метода CTable::SortData() начинал сортировку в противоположном направлении от текущего. Также добавлены методы для получения текущего направления сортировки и индекса отсортированного столбца. Теперь, если таблица была отсортирована пользователем вручную (кликом по заголовку), а затем данные в таблице были обновлены не в той же последовательности, то, выяснив текущее направление сортировки, можно её восстановить.
//+------------------------------------------------------------------+ //| Класс для создания нарисованной таблицы | //+------------------------------------------------------------------+ class CTable : public CElement { public: ... //--- Сортировать данные по указанному столбцу void SortData(const uint column_index=0,const int direction=WRONG_VALUE); //--- (1) Текущее направление сортировки, (2) индекс отсортированного массива int IsSortDirection(void) const { return(m_last_sort_direction); } int IsSortedColumnIndex(void) const { return(m_is_sorted_column_index); } ... }; //+------------------------------------------------------------------+ //| Сортировать данные по указанному столбцу | //+------------------------------------------------------------------+ void CTable::SortData(const uint column_index=0,const int direction=WRONG_VALUE) { //--- Выйти, если выходим за пределы таблицы if(column_index>=m_columns_total) return; //--- Индекс, с которого нужно начать сортировку uint first_index=0; //--- Последний индекс uint last_index=m_rows_total-1; //--- Без контроля направления пользователем if(direction==WRONG_VALUE) { //--- В первый раз будет отсортировано по возрастанию, а затем каждый раз в противоположном направлении if(m_is_sorted_column_index==WRONG_VALUE || column_index!=m_is_sorted_column_index || m_last_sort_direction==SORT_DESCEND) m_last_sort_direction=SORT_ASCEND; else m_last_sort_direction=SORT_DESCEND; } else { m_last_sort_direction=(ENUM_SORT_MODE)direction; } //--- Запомним индекс последнего отсортированного столбца данных m_is_sorted_column_index=(int)column_index; //--- Сортировка QuickSort(first_index,last_index,column_index,m_last_sort_direction); }
Ещё одно маленькое дополнение добавлено в класс CKeys в метод CKeys::KeySymbol(). Раньше цифровая клавиатура (отдельный блок клавиш в правой части клавиатуры) не обрабатывалась. Теперь можно вводить цифры, а также специальные символы и с этого раздела клавиатуры.
//+------------------------------------------------------------------+ //| Возвращает символ нажатой клавиши | //+------------------------------------------------------------------+ string CKeys::KeySymbol(const long key_code) { string key_symbol=""; //--- Если нужно ввести пробел (клавиша "Space") if(key_code==KEY_SPACE) { key_symbol=" "; } //--- Если нужно ввести (1) алфавитный символ или (2) символ цифровой клавиши или (3) специальный символ else if((key_code>=KEY_A && key_code<=KEY_Z) || (key_code>=KEY_0 && key_code<=KEY_9) || (key_code>=KEY_NUMLOCK_0 && key_code<=KEY_NUMLOCK_SLASH) || (key_code>=KEY_SEMICOLON && key_code<=KEY_SINGLE_QUOTE)) { key_symbol=::ShortToString(::TranslateKey((int)key_code)); } //--- Вернуть символ return(key_symbol); }
Новые версии классов CTable и CKeys можно скачать в конце статьи.
Заключение
Вы прочли первую часть статьи. В ней мы разобрали, как без особых усилий можно создавать графические интерфейсы для своих программ любого уровня сложности. Вы можете продолжить развивать эту программу и использовать её в своих целях. Во второй части статьи я покажу, как работать с графическим интерфейсом, а самое главное — как наполнить его функционалом.
Ниже вы можете загрузить к себе на компьютер файлы для тестов и более подробного изучения представленного в статье кода.
Наименование файла | Комментарий |
---|---|
MQL5\Experts\TradePanel\TradePanel.mq5 | Торговый эксперт для ручной торговли с графическим интерфейсом |
MQL5\Experts\TradePanel\Program.mqh | Файл с классом программы |
MQL5\Experts\TradePanel\CreateGUI.mqh | Файл с реализацией методов для создания графического интерфейса из класса программы в файле Program.mqh |
MQL5\Include\EasyAndFastGUI\Controls\Table.mqh | Обновлённый класс CTable |
MQL5\Include\EasyAndFastGUI\Keys.mqh | Обновлённый класс CKeys |
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Здравствуйте. Как исправить ошибку при компиляции?:
'ON_END_CREATE_GUI' - undeclared identifier Program.mqh 307 29
Здравствуйте. Как исправить ошибку при компиляции?:
'ON_END_CREATE_GUI' - undeclared identifier Program.mqh 307 29
Обновить библиотеку:
EasyAndFastGUI - библиотека для создания графических интерфейсов
И файлы библиотеки приложенные к статье.
Анатолий, подскажите пож-ста вот по такому моменту.
На Рис.11 добавлен стандартный график. Вопрос такой. Он имеет уникальный id? Либо я плохо искал, либо, куда-то далеко он запрятан. Задача у меня такая: на этом добавленном графике нарисовать свой небольшой канвас. Спасибо за огромный труд по графической библиотеке.
Пытаюсь постепенно ассимилировать сей огромный и многоликий GUI комплекс, за который автору большое спасибо, но возникают вопросы.
Зачем несколько раз вызывать AddToElementsArray в процессе создания одного элемента? Если, например, таб входит в состав окна, то вызов m_tabs1.AddToElementsArray(0,m_date_scale) не должен ли автоматически вызывать CWndContainer::AddToElementsArray у родителя (насколько я понял терминологию, родители называются главными элементами, и у всех элементов они должны быть, и все они должны быть внутри CWndContainer, то есть все связи для автоматического вызова есть)?
В принципе, наличие вызовов двух "встречных" по смыслу методов (child.MainPointer(parent) и AddToElementsArray(child)) при создании элементов выглядит неоптимальным. Почему бы установку отношений начальник подчиненный не развернуть в противоположную сторону и не объединить в одном вызове а-ля parent.Add(child), который внутри сам сделает AddToElementsArray и child.MainPointer(parent) если надо?
Почему для табов нужно делать AddToElementsArray до создания объекта, а для CWndContainer после?
Почему не сделать возможной установку всех опций перед созданием элемента? Сейчас получается, что свойства искусственно делятся на 2 части: которые нужно установить до вызова Create, и те, которые нужно вызвать позже. А что мешает состояние флага и текста задавать в той же группе операторов, где указываются размер, опции и пр.?
Зачем CWndContainer::AddToElementsArray первым параметром 0, если в подавляющем большинстве случаев используется одно окно и индекс можно сделать вторым опциональным параметром.
Не было идеи унифицировать создание разнотипных элементов с помощью единого виртуального метода Create? Сейчас у каждого типа элемента своя собственная функция CreateBlaBlaBla - а зачем так?
Почему в методах-помощниках (таких как CreateComboBoxTF и пр.) через параметры задается только позиция элемента, а его размер указывается внутри? С учетом выравнивания, размер и позиция должны меняться "синхронно", в одном месте программы, т.е. если координаты сочтены достойными передачи извне в качестве параметров, то и размер там же должен быть.
В статье https://www.mql5.com/ru/articles/3366 был приведен пример эксперта со всеми контролами, но он несовместим с последней версией. Есть обновленный пример?
Файл TradePanel.mq5 не компилируется!