
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 жалуется "'CMasterWindowsEXE::Run' - cannot call protected member function ClassMasterWindows.mqh" на строку "WinEXE.Run()". Подскажите как бороться?
Библиотека MasterWindows доступна в CodeBase. Там же есть примеры использования этой библиотеки в панелях [1, 2, 3].
Попробуйте скомпилированный файл.
Сергей здравствуйте !
... скачал ваш скомпилированный файл (из поста #4) для ознакомления с возможностями ... закинул его в в папку \MQL5\Experts , но он НЕ НАНОСИТСЯ на график (!) :(
Рекомендую обновить MasterWindows. Теперь одновременно генерируются два файла: советник и индикатор.
... скачал из этого поста ... при компиляции выдаёт ошибки :
... скачал из этого поста ... при компиляции выдаёт ошибки :
похоже с не с того начинаете,
надо начинать с основ программирования MQL5, а потом уже классы и пытаться панельку построить и встроить торговые функции
если надо торговать этой панелькой, то лучше купить готовую, выбор есть, а учиться программированию надо вообще не с этой статьи.
если и соберете тут панельку, что дальше с ней делать будете? тот кто сможет вам сделать все хотелки вами описанные, тот скажет ваши наработки эти не нужны.... и использует с нуля свои.
а тот кто просто соображает в программировании, а не панельках, тот эти торговые хотелки для панельки будет вам пол года городить под ваш эскиз из статьи.
add
эта статья может быть полезна тем кто хочет улучшить свои процедурные навыки, до Классов.