English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
MQL5 для "чайников": Как проектировать и конструировать классы объектов

MQL5 для "чайников": Как проектировать и конструировать классы объектов

MetaTrader 5Эксперты | 13 апреля 2010, 13:02
14 096 48
Sergey Pavlov
Sergey Pavlov

Введение в объектно-ориентированное программирование (ООП)

ВОПРОС "чайника": Имея самое смутное представление о процедурном программировании, можно ли освоить ООП и использовать его в написании автоматических торговых стратегий? Или это недоступно обычному разуму?

По большому счёту, написать на объектно-ориентированном языке программирования MQL5 советник или индикатор, можно и без использования принципов ООП. Использование новых технологий в ваших разработках не является обязательным. Поступайте так, как считаете проще. Кроме того, применение ООП тем более не может гарантировать прибыльности создаваемых вами торговых роботов.

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

Итак, на чём базируется технология ООП:

  1. События
  2. Классы объектов

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

Вторым базисом является класс объектов, который в свою очередь покоится на "трёх китах":

  1. Инкапсуляция - защита класса по принципу "чёрного ящика": объект реагирует на события, а его фактическая реализация остаётся неизвестной. 
  2. Наследование - возможность порождать новый класс от известного, с сохранением всех свойств и методов класса-предка.
  3. Полиморфизм - возможность изменять реализацию наследованного метода в классе-потомке.

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

События:
//+------------------------------------------------------------------+
//| 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 Форма конструктора классов (эскиз)

Рис. 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
Класс CCellText
 Класс CCellEdit
Класс CCellEdit
 Класс CCellButton
Класс CCellButton
 Класс CCellButtonType
Класс 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 Библиотека классов ячеек

Рис. 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(0)
 Класс CRowType1(1)
Класс CRowType1(1)
 Класс CRowType1(2)
Класс CRowType1(2)
 Класс CRowType1(3)
Класс CRowType1(3)
 Класс CRowType2
Класс CRowType2
 Класс CRowType3
Класс CRowType3
 Класс CRowType4
Класс CRowType4
 Класс CRowType5
Класс CRowType5
 Класс CRowType6
Класс 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 Процесс визуального проектирования

Рис. 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

Рис. 4 Советник project1 - результат визуального проектирования классов

Заключение

  1. Проектировать классы нужно поэтапно. Разбивая задачу на модули, для каждого создаётся свой класс. Модули, в свою очередь, разбиваются на микромодули из производных или базовых классов.
  2. Старайтесь не перегружать базовые классы встроенными методами - их количество должно быть минимальным.
  3. Проектирование классов с помощью среды визуального конструирования - это очень просто, причём для любого "чайника", поскольку код генерируется автоматически.

Размещение вложенных файлов:

  • masterwindows.mq5 - ...\MQL5\Experts\
  • остальные в папке - ...\MQL5\Include\
Прикрепленные файлы |
masterwindows-doc.zip (300.91 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (48)
Aleksandr Brown
Aleksandr Brown | 20 февр. 2016 в 17:18
Начал изучать классы. Наткнулся эту статью. Да, да мне нужно именно такой простенький интерфейс вставить в мой советник. Хотел на файлики автора взглянуть, а там пусто... :-( точнее в папке masterwindows-doc.zip, пустой файл справки MasterWindows-Doc.chm Очень жаль! Может быть уже всё о чём говорилось в этой статье уже давно устарело?
Sergey Pavlov
Sergey Pavlov | 21 февр. 2016 в 04:23
Aleksandr Brown:
Начал изучать классы. Наткнулся эту статью. Да, да мне нужно именно такой простенький интерфейс вставить в мой советник. Хотел на файлики автора взглянуть, а там пусто... :-( точнее в папке masterwindows-doc.zip, пустой файл справки MasterWindows-Doc.chm Очень жаль! Может быть уже всё о чём говорилось в этой статье уже давно устарело?

Проверил. Файл в порядке. 

На всякий случай вставлю сюда. 

Aleksandr Brown
Aleksandr Brown | 21 февр. 2016 в 10:34
Sergey Pavlov:

Проверил. Файл в порядке. 

На всякий случай вставлю сюда. 

Большое спасибо, за проявленное внимание. Признаюсь, это я немного сглупил. Пару дней назад свежую операционку поставил, и службы безопасности блокировали все скачанные файлы из интернета. Точнее программы при помощи которых они открываются запускались, а содержимое скачанного файла не отображалось. Сейчас всё наладил, и прикреплённый в статье файлик тоже норм открывается. Оформлено всё очень красиво и понятно спасибо.
alventa
alventa | 3 мая 2016 в 11:03
При попытке скомпилировать MasterWindows жалуется "'CMasterWindowsEXE::Run' - cannot call protected member function ClassMasterWindows.mqh" на строку "WinEXE.Run()". Подскажите как бороться?
Sergey Pavlov
Sergey Pavlov | 3 мая 2016 в 16:32
alventa:
При попытке скомпилировать MasterWindows жалуется "'CMasterWindowsEXE::Run' - cannot call protected member function ClassMasterWindows.mqh" на строку "WinEXE.Run()". Подскажите как бороться?

Библиотека MasterWindows доступна в CodeBase. Там же есть примеры использования этой библиотеки в панелях [1, 2, 3].

Создание активных панелей управления  на MQL5 для торговли Создание активных панелей управления на MQL5 для торговли
Статья посвящена разработке активных панелей управления на MQL5. Управление элементами интерфейса осуществляется при помощи механизма обработки событий, есть возможность гибкой настройки свойств элементов управления. Реализована работа с позициями а также возможность выставления, модификации и удаления рыночных и отложенных ордеров.
Пример написания игры "Змейка" на MQL5 Пример написания игры "Змейка" на MQL5
В статье рассматривается пример написания игры "Змейка". Создание игр в 5-ой версии языка MQL стало возможным, в первую очередь, благодаря обработке событий. Поддержка объектно-ориентированного программирования значительно упрощает данный процесс. Также вы узнаете особенности обработки событий, примеры работы со стандартной библиотекой MQL5 и способы периодического вызова функций.
Практическое применение баз данных для анализа рынков Практическое применение баз данных для анализа рынков
Работа с данными стала главной задачей современного программного обеспечения, как автономных, так и сетевых прикладных программ. Для ее решения было создано специализированное программное обеспечение - системы управления базами данных (СУБД), которые позволяют структурировать, систематизировать и организовывать данные для их компьютерного хранения и обработки. Что касается трейдинга, то основная масса аналитиков не прибегает к использованию баз данных (БД) в своей работе. Но бывают задачи, где такое решение пришлось бы кстати. В данной статье приводится пример индикатора, который может сохранять и загружать данные из баз как с клиент-серверной, так и с файл-серверной архитектурами.
Создание тиковых индикаторов Создание тиковых индикаторов
В этой статье описывается создание двух индикаторов: строящего тиковый график цены и рисующего "тиковые свечи" - свечи, содержащие заданное число тиков. Каждый из рассмотренных индикаторов записывает поступающие значения цен в файл для построения индикаторов при повторном запуске терминала (эти данные также могут использоваться другими приложениями).