MQL5 для "чайников": Как проектировать и конструировать классы объектов
Введение в объектно-ориентированное программирование (ООП)
ВОПРОС "чайника": Имея самое смутное представление о процедурном программировании, можно ли освоить ООП и использовать его в написании автоматических торговых стратегий? Или это недоступно обычному разуму?
По большому счёту, написать на объектно-ориентированном языке программирования MQL5 советник или индикатор, можно и без использования принципов ООП. Использование новых технологий в ваших разработках не является обязательным. Поступайте так, как считаете проще. Кроме того, применение ООП тем более не может гарантировать прибыльности создаваемых вами торговых роботов.
Однако, переход на новое (объектно-ориентированное) мышление, позволит применять в своих советниках более сложные адаптивные математические модели торговых стратегий, которые самостоятельно будут реагировать на внешние изменения и синхронизироваться с рынком.
Итак, на чём базируется технология ООП:
- События
- Классы объектов
Именно события являются главным базисом. Вся логика программы построена на обработке постоянно поступающих событий. Какая должна быть реакция на них, определяется и описывается в классах. Другими словами, в объекте класса осуществляется перехват и обработка потока событий.
Вторым базисом является класс объектов, который в свою очередь покоится на "трёх китах":
- Инкапсуляция - защита класса по принципу "чёрного ящика": объект реагирует на события, а его фактическая реализация остаётся неизвестной.
- Наследование - возможность порождать новый класс от известного, с сохранением всех свойств и методов класса-предка.
- Полиморфизм - возможность изменять реализацию наследованного метода в классе-потомке.
Основные понятия, лучше всего продемонстрировать в коде эксперта.
События://+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() // обработка события OnInit { return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) // обработка события OnDeinit { } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() // обработка события OnTick { } //+------------------------------------------------------------------+ //| Expert Timer function | //+------------------------------------------------------------------+ void OnTimer() // обработка события OnTimer { } //+------------------------------------------------------------------+ //| Expert Event function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, // обработка события OnChartEvent const long &lparam, const double &dparam, const string &sparam) { }Класс объекта:
class CNew:public CObject { private: int X,Y; void EditXY(); protected: bool on_event; // флаг обработки событий public: // Конструктор класса void CNew(); // Метод обработки события OnChartEvent virtual void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); };Инкапсуляция:
private: int X,Y; void EditXY();Наследование:
class CNew:public CObjectПолиморфизм:
// Метод обработки события OnChartEvent virtual void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam);Виртуальный метод обработки события OnEvent можно переопределить, но имя метода при этом остаётся тем же самым, как и у класса-предка.
2. Проектирование классов
Одним из самых значительных достоинств ООП является расширяемость - это означает, что существующую систему можно заставить работать с новыми компонентами, причем без внесения в нее каких-либо изменений. Компоненты могут быть добавлены на этапе выполнения.
Рассмотрим процесс проектирования на примере создания программы визуального конструирования классов MasterWindows for MQL5.
2.1. I Этап: Эскизный проект
Проектирование начинается с эскизов, сделанных карандашом на листке бумаги. Это один из самых сложных и увлекательных моментов в программировании. Надо продумать не только диалог программы с пользователем (интерфейс), но и организацию обработки данных. Этот процесс может занять не один день. Начать лучше всего с интерфейса, т.к. он может стать (в некоторых случаях, как в нашем например) определяющим при структуризации алгоритма.
Для организации диалога создаваемой программы, воспользуемся формой, похожей на окно приложения Windows (см. эскиз на рис.1). Она содержит строки, а те в свою очередь состоят из ячеек, а ячейки из графических объектов. Итак, уже на этапе эскизного проекта, начинает вырисовываться структура программы и классификация объектов.
Рис. 1 Форма конструктора классов (эскиз)
При достаточно большом количестве строк и ячеек (полей) в окне формы, они построены всего из двух типов графических объектов: OBJ_EDIT и OBJ_BUTTON. Таким образом, определившись с внешним видом, структурой и базовыми объектами создаваемой программы, можно считать, что эскизный проект готов и пора приступать к следующему этапу.
2.2. II Этап: Проектирование базовых классов
Таких классов пока просматривается три, а в дальнейшем к ним могут добавиться (при необходимости) и другие:
- класс ячейка CCell;
- класс строка CRow, состоит из ячеек класса CCell;
- класс окно CWin, состоит из строк класса CRow.
Теперь можно приступать непосредственно к программированию классов, но... не решена очень важная задача - обмен данными между объектами классов. Для таких целей в языке MQL5 существуют кроме обычных переменных, ещё и новый вид - структуры. Конечно, на этом этапе проектирования не видно всех связей и трудно просчитать их. Поэтому, будем постепенно наполнять описания классов и структур по мере продвижения проекта. Тем более что принципы ООП этому не только не препятствуют, а даже наоборот - поощряют подобную технологию программирования.
Структура WinCell:
//+------------------------------------------------------------------+ //| Структура свойств объектов WinCell | //+------------------------------------------------------------------+ struct WinCell { color TextColor; // цвет текста color BGColor; // цвет фона color BGEditColor; // цвет фона при редактировании ENUM_BASE_CORNER Corner; // угол привязки int H; // высота ячейки int Corn; // направление смещения (1;-1) };
Структуры, которые не содержат строки и объекты динамических массивов, называются простыми структурами. Переменные таких структур могут свободно копироваться друг в друга, даже если это разные структуры. Именно такой и является созданная структура. Ее эффективность мы оценим чуть позднее.
Базовый класс CCell:
//+------------------------------------------------------------------+ //| Базовый класс ЯЧЕЙКА CCell | //+------------------------------------------------------------------+ class CCell { protected: bool on_event; // флаг обработки событий ENUM_OBJECT type; // тип ячейки public: WinCell Property; // свойства ячейки string name; // имя ячейки //+---------------------------------------------------------------+ // Конструктор класса void CCell(); virtual // Метод: нарисовать объект void Draw(string m_name, int m_xdelta, int m_ydelta, int m_bsize); virtual // Метод обработки события OnChartEvent void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); };
Базовый класс CRow:
//+------------------------------------------------------------------+ //| Базовый класс СТРОКА CRow | //+------------------------------------------------------------------+ class CRow { protected: bool on_event; // флаг обработки событий public: string name; // имя строки WinCell Property; // свойства строки //+---------------------------------------------------------------+ // Конструктор класса void CRow(); virtual // Метод: нарисовать строку void Draw(string m_name, int m_xdelta, int m_ydelta, int m_bsize); virtual // Метод обработки события OnChartEvent void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); };
Базовый класс CWin:
//+------------------------------------------------------------------+ //| Базовый класс ОКНО CWin | //+------------------------------------------------------------------+ class CWin { private: void SetXY(int m_corner);// Метод расчёта координат protected: bool on_event; // флаг обработки событий public: string name; // имя окна int w_corner; // угол привязки int w_xdelta; // вертикальный отступ int w_ydelta; // горизонтальный отступ int w_xpos; // координата X точки привязки int w_ypos; // координата Y точки привязки int w_bsize; // ширина окна int w_hsize; // высота окна int w_h_corner; // угол привязки HIDE режима WinCell Property; // свойства окна //--- CRowType1 STR1; // объявление строки класса CRowType2 STR2; // объявление строки класса CRowType3 STR3; // объявление строки класса CRowType4 STR4; // объявление строки класса CRowType5 STR5; // объявление строки класса CRowType6 STR6; // объявление строки класса //+---------------------------------------------------------------+ // Конструктор класса void CWin(); // Метод получения данных void SetWin(string m_name, int m_xdelta, int m_ydelta, int m_bsize, int m_corner); virtual // Метод: нарисовать окно формы void Draw(int &MMint[][3], string &MMstr[][3], int count); virtual // Метод обработки события OnEventTick void OnEventTick(); virtual // Метод обработки события OnChartEvent void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); };
Пояснения и рекомендации:
- Во всех (в рамках данного проекта) базовых классах присутствует метод обработки событий. Он необходим для перехвата и трансляции их дальше по цепочке. Без механизма получения и передачи событий, программа (или модуль) теряет интерактивность.
- При разработке базового класса, старайтесь строить его с минимальным числом методов. Затем в классах-потомках, уже реализовывать различные расширения этого класса, которые позволят нарастить функциональность создаваемых объектов.
- Не использовать прямое обращение к внутренним данным другого класса!
2.3. III Этап: Рабочий проект
На этом этапе начинается пошаговое создание программы. Начиная с несущего каркаса, будем наращивать его функциональными компонентами и наполнять содержанием. При этом будем контролировать правильность работы, выполнять отладку с оптимизацией кода и вылавливать появляющиеся ошибки.
Давайте здесь остановимся и рассмотрим технологию создания каркаса, который подойдёт практически для любой программы. Основное требование к нему - он должен быть сразу работоспособным (компилироваться без ошибок и запускаться на выполнение). Разработчики языка позаботились об этом и предлагают в качестве каркаса использовать шаблон советника, который генерируется мастером MQL5.
В качестве примера, предлагаю свой вариант такого шаблона:
1) Программа = советник
//+------------------------------------------------------------------+ //| MasterWindows.mq5 | //| Copyright DC2008 | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "DC2008" #property link "http://www.mql5.com" #property version "1.00" //--- подключаем файл класса главного модуля #include <ClassMasterWindows.mqh> //--- объявление главного модуля CMasterWindows MasterWin; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- запуск главного модуля MasterWin.Run(); return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- деинициализация главного модуля (удаляем весь мусор) MasterWin.Deinit(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- трансляция событий OnTick в главный модуль MasterWin.OnEventTick(); } //+------------------------------------------------------------------+ //| Expert Event function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- трансляция событий OnChartEvent в главный модуль MasterWin.OnEvent(id,lparam,dparam,sparam); }
Это полностью готовый код советника. Больше ни каких изменений, на протяжении всего проекта, в него вносить не надо!
2) Главный модуль = класс
Все основные и вспомогательные модули проекта начнут своё развитие
именно отсюда. Такой подход облегчает программирование сложных многомодульных
проектов и облегчает поиск возможных ошибок. А найти их бывает очень трудно. Иногда проще и быстрее написать новый проект, чем искать неуловимые "глюки".
//+------------------------------------------------------------------+ //| ClassMasterWindows.mqh | //| Copyright DC2008 | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "DC2008" #property link "http://www.mql5.com" //+------------------------------------------------------------------+ //| Главный модуль: класс CMasterWindows | //+------------------------------------------------------------------+ class CMasterWindows { protected: bool on_event; // флаг обработки событий public: // Конструктор класса void CMasterWindows(); // Метод запуска главного модуля (основной алгоритм) void Run(); // Метод деинициализации (удаляем весь мусор) void Deinit(); // Метод перехвата и обработки событий OnTick() void OnEventTick(); // Метод перехвата и обработки событий OnChartEvent() void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); };
Ниже приведу примерное начальное описание основных методов класса.
//+------------------------------------------------------------------+ //| Конструктор класса CMasterWindows | //+------------------------------------------------------------------+ void CMasterWindows::CMasterWindows() { //--- инициализация членов класса on_event=false; // запрещаем обработку событий } //+------------------------------------------------------------------+ //| Метод запуска главного модуля (основной алгоритм) | //+------------------------------------------------------------------+ void CMasterWindows::Run() { //--- основной функционал класса: запускает дополнительные модули ObjectsDeleteAll(0,0,-1); Comment("MasterWindows for MQL5 © DC2008"); //--- on_event=true; // разрешаем обработку событий } //+------------------------------------------------------------------+ //| Метод деинициализации (удаляем весь мусор) | //+------------------------------------------------------------------+ void CMasterWindows::Deinit() { //--- ObjectsDeleteAll(0,0,-1); Comment(""); } //+------------------------------------------------------------------+ //| Метод перехвата и обработки событий OnTick() | //+------------------------------------------------------------------+ void CMasterWindows::OnEventTick() { if(on_event) // обработка событий разрешена { //--- } } //+------------------------------------------------------------------+ //| Метод перехвата и обработки событий OnChartEvent() | //+------------------------------------------------------------------+ void CMasterWindows::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(on_event) // обработка событий разрешена { //--- } }
3) Библиотека базовых и производных классов
В библиотеке может быть сколько угодно производных классов и группировать их лучше в отдельных включаемых файлах вместе с базовым классом (если он есть). Так легче будет вносить необходимые изменения и дополнения, а так же - находить ошибки.
Итак, каркас программы есть. Проверяем его работоспособность: компилируем и запускаем на выполнение. Если проверка прошла успешно, то можно приступить к наполнению проекта дополнительными модулями.
Начнём с подключения производных классов и в первую очередь с ячеек:
Название класса |
Изображение |
---|---|
Класс CCellText |
|
Класс CCellEdit |
|
Класс CCellButton |
|
Класс CCellButtonType |
Табл. 1 Библиотека классов ячеек
Подробно рассмотрим создание только одного производного класса CCellButtonType. Этот класс создаёт кнопки разных типов.
//+------------------------------------------------------------------+ //| Класс ЯЧЕЙКА: CCellButtonType | //+------------------------------------------------------------------+ class CCellButtonType:public CCell { public: // Конструктор класса void CCellButtonType(); virtual // Метод: нарисовать объект void Draw(string m_name, int m_xdelta, int m_ydelta, int m_type); }; //+------------------------------------------------------------------+ //| Конструктор класса CCellButtonType | //+------------------------------------------------------------------+ void CCellButtonType::CCellButtonType() { type=OBJ_BUTTON; on_event=false; // запрещаем обработку событий } //+------------------------------------------------------------------+ //| Метод Draw класса CCellButtonType | //+------------------------------------------------------------------+ void CCellButtonType::Draw(string m_name, int m_xdelta, int m_ydelta, int m_type) { //--- создаём объект с модифицированным именем if(m_type<=0) m_type=0; name=m_name+".Button"+(string)m_type; if(ObjectCreate(0,name,type,0,0,0,0,0)==false) Print("Функция ",__FUNCTION__," ошибка ",GetLastError()); //--- инициализируем свойства объекта ObjectSetInteger(0,name,OBJPROP_COLOR,Property.TextColor); ObjectSetInteger(0,name,OBJPROP_BGCOLOR,Property.BGColor); ObjectSetInteger(0,name,OBJPROP_CORNER,Property.Corner); ObjectSetInteger(0,name,OBJPROP_XDISTANCE,m_xdelta); ObjectSetInteger(0,name,OBJPROP_YDISTANCE,m_ydelta); ObjectSetInteger(0,name,OBJPROP_XSIZE,Property.H); ObjectSetInteger(0,name,OBJPROP_YSIZE,Property.H); ObjectSetInteger(0,name,OBJPROP_SELECTABLE,0); if(m_type==0) // Кнопка Hide { ObjectSetString(0,name,OBJPROP_TEXT,CharToString(MIN_WIN)); ObjectSetString(0,name,OBJPROP_FONT,"Webdings"); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,12); } if(m_type==1) // Кнопка Close { ObjectSetString(0,name,OBJPROP_TEXT,CharToString(CLOSE_WIN)); ObjectSetString(0,name,OBJPROP_FONT,"Wingdings 2"); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,8); } if(m_type==2) // Кнопка Return { ObjectSetString(0,name,OBJPROP_TEXT,CharToString(MAX_WIN)); ObjectSetString(0,name,OBJPROP_FONT,"Webdings"); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,12); } if(m_type==3) // Кнопка Plus { ObjectSetString(0,name,OBJPROP_TEXT,"+"); ObjectSetString(0,name,OBJPROP_FONT,"Arial"); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,10); } if(m_type==4) // Кнопка Minus { ObjectSetString(0,name,OBJPROP_TEXT,"-"); ObjectSetString(0,name,OBJPROP_FONT,"Arial"); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,13); } if(m_type==5) // Кнопка PageUp { ObjectSetString(0,name,OBJPROP_TEXT,CharToString(PAGE_UP)); ObjectSetString(0,name,OBJPROP_FONT,"Wingdings 3"); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,8); } if(m_type==6) // Кнопка PageDown { ObjectSetString(0,name,OBJPROP_TEXT,CharToString(PAGE_DOWN)); ObjectSetString(0,name,OBJPROP_FONT,"Wingdings 3"); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,8); } if(m_type>6) // Кнопка пустая { ObjectSetString(0,name,OBJPROP_TEXT,""); ObjectSetString(0,name,OBJPROP_FONT,"Arial"); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,13); } on_event=true; // разрешаем обработку событий }
Необходимые пояснения:
- В конструкторе класса вводим запрет на обработку событий. Это нужно для того, чтобы подготовить объект к работе и не отвлекаться на поступающие события. После завершения всех необходимых операций, мы разрешим такую обработку, и объект начнёт полноценно функционировать.
- Метод Draw использует внутренние и получает внешние данные. Поэтому следовало бы их предварительно проверить на соответствие, а уж потом только обрабатывать, чтобы избежать исключительных ситуаций. Но мы этого, в данном конкретном случае, делать не будем. Почему? Представьте себе, что объект класса - это солдат, а солдатам не обязательно знать планы генералов. Их дело - чётко, быстро и безукоснительно выполнять приказы своих командиров, а не анализировать получаемые команды и принимать самостоятельные решения. Поэтому, ответственность за соответствие внешних данных мы возложим на тот класс, который будет вызывать этот.
Теперь надо протестировать всю библиотеку ячеек. Для этого вставим в главный модуль (временно для проверки) следующий программный код и запустим советник на выполнение.
//--- Подключаем файл классов #include <ClassUnit.mqh> //+------------------------------------------------------------------+ //| Главный модуль: класс CMasterWindows | //+------------------------------------------------------------------+ class CMasterWindows { protected: bool on_event; // флаг обработки событий WinCell Property; // свойства строки CCellText Text; CCellEdit Edit; CCellButton Button; CCellButtonType ButtonType; public: // Конструктор класса void CMasterWindows(); // Метод запуска главного модуля (основной алгоритм) void Run(); // Метод деинициализации (удаляем весь мусор) void Deinit(); // Метод перехвата и обработки событий OnTick() void OnEventTick(); // Метод перехвата и обработки событий OnChartEvent() void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); }; //+------------------------------------------------------------------+ //| Метод запуска главного модуля (основной алгоритм) | //+------------------------------------------------------------------+ void CMasterWindows::Run() { //--- основной функционал класса: запускает дополнительные модули ObjectsDeleteAll(0,0,-1); Comment("MasterWindows for MQL5 © DC2008"); //--- текстовое поле Text.Draw("Text",50,50,150,"Текстовое поле"); //--- поле редактора Edit.Draw("Edit",205,50,150,"значение по умолчанию",true); //--- БОЛЬШАЯ КНОПКА Button.Draw("Button",50,80,200,"БОЛЬШАЯ КНОПКА"); //--- кнопка Hide ButtonType.Draw("type0",50,100,0); //--- кнопка Close ButtonType.Draw("type1",70,100,1); //--- кнопка Return ButtonType.Draw("type2",90,100,2); //--- кнопка Plus ButtonType.Draw("type3",110,100,3); //--- кнопка Minus ButtonType.Draw("type4",130,100,4); //--- кнопка None ButtonType.Draw("type5",150,100,5); //--- кнопка None ButtonType.Draw("type6",170,100,6); //--- кнопка None ButtonType.Draw("type7",190,100,7); //--- on_event=true; // разрешаем обработку событий }
И не забудем осуществить трансляцию событий для вызываемых классов! Если этого не сделать, управление объектами будет затруднено или вообще невозможно.
//+------------------------------------------------------------------+ //| Метод перехвата и обработки событий OnChartEvent() | //+------------------------------------------------------------------+ void CMasterWindows::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(on_event) // обработка событий разрешена { //--- трансляция событий для объектов класса ячеек Text.OnEvent(id,lparam,dparam,sparam); Edit.OnEvent(id,lparam,dparam,sparam); Button.OnEvent(id,lparam,dparam,sparam); ButtonType.OnEvent(id,lparam,dparam,sparam); } }
В результате получим на экране все имеющиеся варианты объектов библиотеки классов ячеек.
Рис. 2 Библиотека классов ячеек
Проверяем работоспособность и реакцию объектов на события:
- Вводим в поле редактора вместо "значения по умолчанию" различные переменные: значения изменяются - тестирование прошло успешно.
- Нажимаем на кнопки, они занимают нажатое положение и залипают до следующего нажатия. Такая реакция не устраивает. Нам надо, чтобы кнопка после нажатия вернулась в исходное положение самостоятельно. И вот тут можно продемонстрировать мощь ООП - возможность наследования. У нас в программе может быть задействован не один десяток кнопок и для каждой добавлять нужную функциональность не надо. Достаточно изменить базовый класс CCell, и все объекты классов-наследников, чудесным образом начинают работать так, как надо!
//+------------------------------------------------------------------+ //| Метод обработки события OnChartEvent класса CCell | //+------------------------------------------------------------------+ void CCell::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(on_event) // обработка событий разрешена { //--- нажатие кнопки if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".Button",0)>0) { if(ObjectGetInteger(0,sparam,OBJPROP_STATE)==1) { //--- если кнопка залипла Sleep(TIME_SLEEP); ObjectSetInteger(0,sparam,OBJPROP_STATE,0); ChartRedraw(); } } } }
Итак, библиотека классов ячеек протестирована и подключена к проекту.
Следующим шагом добавляем библиотеку строк:
Название класса |
Изображение |
---|---|
Класс CRowType1(0) |
|
Класс CRowType1(1) |
|
Класс CRowType1(2) |
|
Класс CRowType1(3) |
|
Класс CRowType2 |
|
Класс CRowType3 |
|
Класс CRowType4 |
|
Класс CRowType5 |
|
Класс CRowType6 |
Табл. 2 Библиотека классов строк
и точно так же тестируем. После завершения всех проверок, приступаем к следующему этапу.
2.4 IV Этап: Сборка проекта
Итак, все необходимые модули созданы и протестированы. Теперь приступаем к сборке проекта. Первым делом создаём оболочку: форму окна как на рис.1 и наполняем его функциональностью, т.е. программируем реакцию всех элементов и модулей на поступающие события.
Для этого у нас есть готовый каркас программы и заготовка главного модуля. Вот с него и начнём. Он является классом-наследником базового класса CWin, следовательно, все публичные методы и поля класса-предка достались ему по наследству. Поэтому, достаточно только переопределить несколько методов и новый класс CMasterWindows готов:
//--- Подключаем файлы классов #include <ClassWin.mqh> #include <InitMasterWindows.mqh> #include <ClassMasterWindowsEXE.mqh> //+------------------------------------------------------------------+ //| класс CMasterWindows | //+------------------------------------------------------------------+ class CMasterWindows:public CWin { protected: CMasterWindowsEXE WinEXE; // исполняемый модуль public: void Run(); // метод запуска главного модуля void Deinit(); // метод деинициализации virtual // метод обработки события OnChartEvent void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); }; //+------------------------------------------------------------------+ //| Метод Deinit класса CMasterWindows | //+------------------------------------------------------------------+ void CMasterWindows::Deinit() { //---(удаляем весь мусор) ObjectsDeleteAll(0,0,-1); Comment(""); } //+------------------------------------------------------------------+ //| Метод Run класса CMasterWindows | //+------------------------------------------------------------------+ void CMasterWindows::Run() { ObjectsDeleteAll(0,0,-1); Comment("MasterWindows for MQL5 © DC2008"); //--- создаём окно конструктора и запускаем исполняемый модуль SetWin("CWin1",1,30,250,CORNER_RIGHT_UPPER); Draw(Mint,Mstr,21); WinEXE.Init("CWinNew",30,18); WinEXE.Run(); } //+------------------------------------------------------------------+ //| Метод обработки события OnChartEvent класса CMasterWindows | //+------------------------------------------------------------------+ void CMasterWindows::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(on_event) // обработка событий разрешена { //--- нажатие кнопки Close в главном окне if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,"CWin1",0)>=0 && StringFind(sparam,".Button1",0)>0) { ExpertRemove(); } //--- трансляция событий OnChartEvent STR1.OnEvent(id,lparam,dparam,sparam); STR2.OnEvent(id,lparam,dparam,sparam); STR3.OnEvent(id,lparam,dparam,sparam); STR4.OnEvent(id,lparam,dparam,sparam); STR5.OnEvent(id,lparam,dparam,sparam); STR6.OnEvent(id,lparam,dparam,sparam); WinEXE.OnEvent(id,lparam,dparam,sparam); } }
Сам по себе главный модуль получился небольшим, т.к. он отвечает только за создания окна приложения. Затем передаёт управление исполняемому модулю WinEXE, в котором и происходит всё самое интересное - реакция на поступающие события.
Для обмена данными между объектами ранее была создана простая структура WinCell и вот теперь становится понятным преимущества такого подхода. Копирование всех членов структуры происходит очень разумно и компактно:
STR1.Property=Property; STR2.Property=Property; STR3.Property=Property; STR4.Property=Property; STR5.Property=Property; STR6.Property=Property;
На этом этапе можно завершить дальнейшее подробное рассмотрение проектирования классов и перейти к технологии визуального их конструирования, которая на порядок ускоряет процесс создания новых классов.
3. Визуальное конструирование классов
Гораздо быстрее и значительно нагляднее класс можно сконструировать в среде визуального проектирования MasterWindows for MQL5:
Рис. 3 Процесс визуального проектирования
Всё что требуется от разработчика - это нарисовать средствами MasterWindows форму окна и потом только определить реакцию на планируемые события. Сам код создаётся автоматически. Вот и всё! Проект готов.
Пример сгенерированного кода класса CMasterWindows и самого советника приведен на рис.4 (файл создаётся в папке ...\MQL5\Files):
//****** Проект: project1.mq5 //+------------------------------------------------------------------+ //| Программный код сгенерирован MasterWindows Copyright DC2008 | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "DC2008" //--- Подключаем файлы классов #include <ClassWin.mqh> int Mint[][3]= { {1,0,0}, {2,100,0}, {1,100,0}, {3,100,0}, {4,100,0}, {5,100,0}, {6,100,50}, {} }; string Mstr[][3]= { {"Новое окно","",""}, {"NEW1","new1",""}, {"NEW2","new2",""}, {"NEW3","new3",""}, {"NEW4","new4",""}, {"NEW5","new5",""}, {"NEW6","new6",""}, {} }; //+------------------------------------------------------------------+ //| класс CMasterWindows (Главный модуль) | //+------------------------------------------------------------------+ class CMasterWindows:public CWin { public: void CMasterWindows() {on_event=false;} void Run(); // метод запуска главного модуля void Deinit() {ObjectsDeleteAll(0,0,-1);Comment("");} virtual void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); }; //+------------------------------------------------------------------+ //| Метод Run класса CMasterWindows | //+------------------------------------------------------------------+ void CMasterWindows::Run() { ObjectsDeleteAll(0,0,-1); Comment("Программный код сгенерирован MasterWindows for MQL5 © DC2008"); //--- создаём главное окно и запускаем исполняемый модуль SetWin("CWin1",50,100,250,CORNER_LEFT_UPPER); Draw(Mint,Mstr,7); } //+------------------------------------------------------------------+ //| Метод обработки события OnChartEvent класса CMasterWindows | //+------------------------------------------------------------------+ void CMasterWindows::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(on_event) // обработка событий разрешена { //--- нажатие кнопки Close в главном окне if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,"CWin1",0)>=0 && StringFind(sparam,".Button1",0)>0) { ExpertRemove(); } //--- трансляция событий OnChartEvent STR1.OnEvent(id,lparam,dparam,sparam); STR2.OnEvent(id,lparam,dparam,sparam); STR3.OnEvent(id,lparam,dparam,sparam); STR4.OnEvent(id,lparam,dparam,sparam); STR5.OnEvent(id,lparam,dparam,sparam); STR6.OnEvent(id,lparam,dparam,sparam); } } //--- объявление главного модуля CMasterWindows MasterWin; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- запуск главного модуля MasterWin.Run(); return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- деинициализация главного модуля (удаляем весь мусор) MasterWin.Deinit(); } //+------------------------------------------------------------------+ //| Expert Event function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- трансляция событий OnChartEvent в главный модуль MasterWin.OnEvent(id,lparam,dparam,sparam); } //+------------------------------------------------------------------+
При запуске которого, получим спроектированное окно:
Рис. 4 Советник project1 - результат визуального проектирования классов
Заключение
- Проектировать классы нужно поэтапно. Разбивая задачу на модули, для каждого создаётся свой класс. Модули, в свою очередь, разбиваются на микромодули из производных или базовых классов.
- Старайтесь не перегружать базовые классы встроенными методами - их количество должно быть минимальным.
- Проектирование классов с помощью среды визуального конструирования - это очень просто, причём для любого "чайника", поскольку код генерируется автоматически.
Размещение вложенных файлов:
- masterwindows.mq5 - ...\MQL5\Experts\
- остальные в папке - ...\MQL5\Include\
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Начал изучать классы. Наткнулся эту статью. Да, да мне нужно именно такой простенький интерфейс вставить в мой советник. Хотел на файлики автора взглянуть, а там пусто... :-( точнее в папке masterwindows-doc.zip, пустой файл справки MasterWindows-Doc.chm Очень жаль! Может быть уже всё о чём говорилось в этой статье уже давно устарело?
Проверил. Файл в порядке.
На всякий случай вставлю сюда.
Проверил. Файл в порядке.
На всякий случай вставлю сюда.
При попытке скомпилировать MasterWindows жалуется "'CMasterWindowsEXE::Run' - cannot call protected member function ClassMasterWindows.mqh" на строку "WinEXE.Run()". Подскажите как бороться?
Библиотека MasterWindows доступна в CodeBase. Там же есть примеры использования этой библиотеки в панелях [1, 2, 3].