![“傻瓜式”MQL:如何设计和构建对象类](https://c.mql5.com/i/articles/overlay.png)
“傻瓜式”MQL:如何设计和构建对象类
面向对象编程 (OOP) 简介
“傻瓜式”问题:只对过程程序设计有非常模糊的认识,有可能掌握 OOP 并使用它编写自动交易策略吗?或该任务是否已超出了普通用户的能力范畴?
一般而言,使用面向对象编程语言编写 MQL5“EA 交易”或指标而不使用面向对象编程原理是可能的。在您的开发中使用新技术并不是强制性的。选择您认为最简单的方式。此外,OOP 的应用更是无法保证您创建的交易机器人的盈利能力。
然而,到新方法(面向对象)的过渡为将更复杂的交易策略自适应数学模型应用至“EA 交易”开辟了道路,“EA 交易”将对外部改变做出响应并与市场保持同步。
那么,我们来看看 OOP 基于的技术:
- 事件
- 对象类
事件是 OOP 的主要基础。程序的整个逻辑建立在对不断传入的事件的处理上。对这些事件的适当响应在对象类中定义和说明。换言之,类对象通过截获和处理事件流工作。
第二个基础是对象的类,这反过来取决于“三大支柱”:
- 封装 - 基于“黑盒”原则保护类:对象对事件做出响应,但其实际实施仍然是未知的。
- 继承 - 从现有类创建新类的可能性,同时保留“祖先”类的所有属性和方法。
- 多态性 - 更改“后代”类的继承方法的实施的能力。
在“EA 交易”代码中,基本概念得到了很好的诠释。
//+------------------------------------------------------------------ //| EA 初始化函数 | //+------------------------------------------------------------------ int OnInit() // OnInit 事件处理 { return(0); } //+------------------------------------------------------------------ //| EA 去初始化函数 | //+------------------------------------------------------------------ void OnDeinit(const int reason) // OnDeInit 事件处理 { } //+------------------------------------------------------------------ //| EA 的 tick 函数 | //+------------------------------------------------------------------ void OnTick() // OnTick事件处理 { } //+------------------------------------------------------------------ //| EA 时间函数 | //+------------------------------------------------------------------ void OnTimer() // OnTimer事件处理 { } //+------------------------------------------------------------------ //| EA 图表事件函数 | //+------------------------------------------------------------------ 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(); // OnChart 事件处理方法 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
// OnChart 事件处理方法 virtual void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
此方法的 virtual 修饰符表示 OnEvent 处理程序可被覆盖,但方法的名称在此情况下与祖先类的方法名称保持一样。
2. 设计类
OOP 最重要的优势之一是其可扩展性 - 这意味着现有系统能够使用新组件而无需对其作出任何更改。新组件可在此阶段添加。
我们通过为 MQL5 创建 MasterWindows 类的视觉设计程序来讨论设计过程。
2.1. 阶段 I:项目草图
设计过程起始于用铅笔在纸上绘制草图。这是编程中最具挑战和令人兴奋的时刻之一。我们不仅要考虑程序和用户(接口)之间的对话,还要考虑数据处理的组织。这个过程可能需要超过一天的时间。我们最好从接口开始我们的工作,因为在构建算法时它可以起到(在某些情况下,比如在我们的例子中)决定性的作用。
对于所创建程序的对话的组织,我们将使用类似于 Windows 应用程序窗口的形式(请参见图 1 中的草图)。它包含线条,这些线条反过来形成单元,以及图形对象的单元。因此,早在概念设计阶段,我们开始查看程序的结构和对象的分类。
图 1. 类构造器的形式(草图)
形式中具有足够数量的行和单元(字段),它们仅构建两种类型的图形对象:OBJ_EDIT 和 OBJ_BUTTON。因此,一旦我们确定视觉外观、结构和程序创建的基本对象,我们可以认为设计的草图已就绪,是时候前往下一阶段了。
2.2 阶段 II:设计基类
到目前为止有三个这样的类,可稍后添加更多的类(如必要):
- 单元类 CCell;
- 行类 CRow,由类 CCell 的单元组成;
- 窗口类 CWin,由类 CRow 的行组成。
至此,我们可以直接进行类的编程,但...我们还需要解决一个非常重要的任务 - 类的对象之间的数据交换。为此,除了普通变量,MQL5 语言还包含一种新类型 - 结构。当然,在设计的这一阶段,我们无法看到所有连接,并且计算它们是困难的。因此,我们将随着项目的进展,逐步填入类和结构的描述。此外,OOP 的原则不但不会与这种编程技术发生冲突,相反,还鼓励如此。
WinCell 结构:
struct WinCell { color TextColor; //文本颜色 color BGColor; // 背景颜色 color BGEditColor; // 编辑时的背景颜色 ENUM_BASE_CORNER Corner; // 标定角 int H; // 元素高度 int Corn; // 位移方向(1;-1) };
不包含动态数组字符串和对象的结构称为简单结构。这种结构的变量之间可自由地进行复制,即使它们是不同的结构。已建立的结构正是这种类型。稍后,我们将评估其有效性。
基类 CCell:
//+------------------------------------------------------------------ //| CCell 基类 | //+------------------------------------------------------------------ class CCell { private: 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 // 事件处理方法 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 // 事件处理方法 void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); };
基类 CWin:
//+------------------------------------------------------------------ //| 基类 CWin(WINDOW) | //+------------------------------------------------------------------ class CWin { private: void SetXY(int m_corner); //坐标 protected: bool on_event; // 事件处理标识 public: string name; // 窗口名称 int w_corner; // 窗口角 int w_xdelta; // 垂直delta int w_ydelta; // 水平detla int w_xpos; // X 坐标 int w_ypos; // Y 坐标 int w_bsize; // 窗口宽度 int w_hsize; // 窗口高度 int w_h_corner; // 隐藏模式的角落 WinCell Property; // 属性 //--- CRowType1 STR1; // CRowType1 CRowType2 STR2; // CRowType2 CRowType3 STR3; // CRowType3 CRowType4 STR4; // CRowType4 CRowType5 STR5; // CRowType5 CRowType6 STR6; // CRowType6 //+---------------------------------------------------------------+ // 类的构造函数 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 // OnChart 事件处理方法 void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); };
说明及建议:
- 所有基类(在此项目中)包含处理事件的方法。它们都是截获事件和进一步沿链传递事件必需的类。没有接收和发送事件的机制,程序(或模块)将失去其交互性。
- 在开发基类时,尝试使用最少的方法来构建它。然后,在“后代”类中实施该类的各种扩展,这将提升创建的对象的功能性。
- 请勿直接要求其他类的内部数据!
2.3 阶段 III:运转项目
至此,我们将开始程序的逐步创建过程。从支持框架入手,我们将增加其功能组件并填入内容。在此过程中,我们将监控工作的正确性、使用优化的代码进行调试并跟踪出现的错误。
让我们在此稍作停留,考虑框架创建的技术,它将用于几乎任何程序。主要的要求 - 它应该是立即可操作的(编译无错误,执行即运行)。语言设计人员应注意这点,我们建议他们使用“MQL5 向导”生成的“EA 交易”模板作为框架。
作为示例,让我们考虑该模板的我们自己的版本:
1) 程序 =“EA 交易”
//+------------------------------------------------------------------ //| 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; //+------------------------------------------------------------------ //| EA 初始化函数 | //+------------------------------------------------------------------ int OnInit() { //--- 加载程序的主模块 MasterWin.Run(); return(0); } //+------------------------------------------------------------------ //| EA 去初始化函数 | //+------------------------------------------------------------------ void OnDeinit(const int reason) { //--- 反初始化主程序模块 MasterWin.Deinit(); } //+------------------------------------------------------------------ //| EA 的 tick 函数 | //+------------------------------------------------------------------ void OnTick() { //--- 调用主模块的 Ontick 事件处理函数 MasterWin.OnEventTick(); } //+------------------------------------------------------------------ //| EA 事件函数 | //+------------------------------------------------------------------ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //---调用主模块的 OnChartEvent 事件处理函数 MasterWin.OnEvent(id,lparam,dparam,sparam); }
这是“EA 交易”的完整代码。整个项目无需进行其他更改!
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; //禁用事件处理 } //+------------------------------------------------------------------ //| 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 ",__FUNCTION__," error ",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) // 关闭按钮 { 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) // 返回按钮 { ObjectSetString(0,name,OBJPROP_TEXT,CharToString(MAX_WIN)); ObjectSetString(0,name,OBJPROP_FONT,"Webdings"); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,12); } if(m_type==3) // 加法按钮 { ObjectSetString(0,name,OBJPROP_TEXT,"+"); ObjectSetString(0,name,OBJPROP_FONT,"Arial"); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,10); } if(m_type==4) // 减法按钮 { ObjectSetString(0,name,OBJPROP_TEXT,"-"); ObjectSetString(0,name,OBJPROP_FONT,"Arial"); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,13); } if(m_type==5) // 向上翻页按钮 { 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) // 向下翻页按钮 { 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; //启用事件处理 } //+------------------------------------------------------------------
必要说明:
- 我们在类构造器中加入事件处理的禁令。这对于准备对象用于工作和消除传入事件的干扰是必要的。在完成所有必要的操作后,我们将允许这样的处理,且对象将开始完全运行。
- 绘制方法使用内部数据并接收外部数据。因此,应首先测试数据的兼容性,然后才处理数据,以避免发生异常情况。但在此特殊示例中,我们不会执行该测试。原因何在?想象一下,类对象是一个士兵,而士兵无需知道将军的计划。他们的任务是明确、快速和严格地执行指挥官的命令,而不是分析收到的命令然后作出独立的决策。因此,在我们使用它们的类之前,必须对所有外部数据进行编译。
现在,我们必须测试整个单元库。为此,我们将在主模块中插入以下代码(临时用于测试目的)并运行“EA 交易”。
//---包含类的文件 #include <ClassUnit.mqh> //+------------------------------------------------------------------ //| 主模块:CMasterWindows 类 | //+------------------------------------------------------------------ class CMasterWindows { protected: bool on_event; // 事件处理标识g WinCell Property; // 元素属性 CCellText Text; CCellEdit Edit; CCellButton Button; CCellButtonType ButtonType; public: // 类的构造函数 void CMasterWindows(); // 主模块的 run 方法(核心算法) void Run(); // 反初始化方法 void Deinit(); // OnTick 事件处理方法 void OnEventTick(); // OnChart 事件处理方法 void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); }; //+------------------------------------------------------------------ //| 主模块的 run 方法(核心算法) | //+------------------------------------------------------------------ void CMasterWindows::Run() { //--- 核心算法 - 它要加载其他额外模块 ObjectsDeleteAll(0,0,-1); Comment("MasterWindows for MQL5 © DC2008"); //--- 文本区 Text.Draw("Text",50,50,150,"Text field"); //--- 编辑区 Edit.Draw("Edit",205,50,150,"default value",true); //--- LARGE 按钮 Button.Draw("Button",50,80,200,"LARGE BUTTON"); //--- 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; // 启用事件处理 }
并且我们不要忘了为结果类传递事件!如果不这样做,项目的处理将变得十分困难或甚至是不可能的。
//+------------------------------------------------------------------ //| CMasterWindows 类图表事件处理方法 | //+------------------------------------------------------------------ void CMasterWindows::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(on_event) // 启用事件处理 { //--- process events for the cell class objects 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. 单元类库
我们来测试工作效率和对象对事件的响应:
- 我们在编辑字段中输入不同的变量来取代“默认”。如果值发生变化,则测试是成功的。
- 我们按下按钮,它们保持按下状态直至它们再次被按。然而,这不是令人满意的响应。我们需要在按下一次后,按钮自动返回其原始状态。而这也是我们可以展示 OOP 优势的地方 - 继承的能力。我们的程序可使用超过一打按钮,分别为每个按钮添加所需的功能性是不必要的。我们只需更改 CCell 基类便已足够,而派生类的所有对象将奇迹般地开始正常工作!
//+------------------------------------------------------------------ //| CCell 类 OnChart 事件处理方法 | //+------------------------------------------------------------------ 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(); // Run 方法 void Deinit(); // 反初始化方法 virtual // OnChart 事件处理方法 void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); }; //+------------------------------------------------------------------ //| CMasterWindows 类反初始化方法 | //+------------------------------------------------------------------ void CMasterWindows::Deinit() { //---删除所有对象 ObjectsDeleteAll(0,0,-1); Comment(""); } //+------------------------------------------------------------------ //| CMasterWindows 类的 Run 方法 | //+------------------------------------------------------------------ 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(); } //+------------------------------------------------------------------ //| CMasterWindows 类事件处理方法 | //+------------------------------------------------------------------ void CMasterWindows::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(on_event) // 启用事件处理 { //--- 在主窗口中点击了关闭按钮 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,"CWin1",0)>=0 && StringFind(sparam,".Button1",0)>0) { ExpertRemove(); } //--- 所有对象的 OnChart 事件处理 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. 类的视觉设计
在 MQL5 的视觉 MasterWindows 设计模式下,类的构建要快得多,也更容易可视化:图 3. 视觉设计过程
开发人员需要做的全部事情就是绘制窗口形式,使用 MasterWindows 形式的方法,简单地确定对计划事件的响应。代码本身是自动创建的。就是这样!项目完成。
CMasterWindows 类和“EA 交易”生成代码的示例请见图 4(一个文件在文件夹 ...\MQL5\Files 中被创建):
//****** Project (Expert Advisor): project1.mq5 //+------------------------------------------------------------------ //| Code has been generated by 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]= { {"New window","",""}, {"NEW1","new1",""}, {"NEW2","new2",""}, {"NEW3","new3",""}, {"NEW4","new4",""}, {"NEW5","new5",""}, {"NEW6","new6",""}, {} }; //+------------------------------------------------------------------ //| CMasterWindows 类(主) | //+------------------------------------------------------------------ class CMasterWindows:public CWin { private: long Y_hide; // 隐藏模式的窗口垂直偏移 long Y_obj; // 窗口的垂直偏移 long H_obj; // 水平窗口偏移 public: bool on_hide; // HIDE 模式的标识 CArrayString units; // 主窗口线条 void CMasterWindows() {on_event=false; on_hide=false;} void Run(); // Run 方法 void Hide(); // 隐藏方法 void Deinit() {ObjectsDeleteAll(0,0,-1); Comment("");} virtual void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); }; //+------------------------------------------------------------------ //| CMasterWindows 类的 Run 方法 | //+------------------------------------------------------------------ void CMasterWindows::Run() { ObjectsDeleteAll(0,0,-1); Comment("Code has been generated by MasterWindows for MQL5 © DC2008"); //--- 创建主窗口并加载可执行对象 SetWin("project1.Exp",50,100,250,CORNER_LEFT_UPPER); Draw(Mint,Mstr,7); } //+------------------------------------------------------------------ //| CMasterWindows 类的 Hide 方法 | //+------------------------------------------------------------------ void CMasterWindows::Hide() { Y_obj=w_ydelta; H_obj=Property.H; Y_hide=ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,0)-Y_obj-H_obj;; //--- if(on_hide==false) { int n_str=units.Total(); for(int i=0; i<n_str; i++) { long y_obj=ObjectGetInteger(0,units.At(i),OBJPROP_YDISTANCE); ObjectSetInteger(0,units.At(i),OBJPROP_YDISTANCE,(int)y_obj+(int)Y_hide); if(StringFind(units.At(i),".Button0",0)>0) ObjectSetString(0,units.At(i),OBJPROP_TEXT,CharToString(MAX_WIN)); } } else { int n_str=units.Total(); for(int i=0; i<n_str; i++) { long y_obj=ObjectGetInteger(0,units.At(i),OBJPROP_YDISTANCE); ObjectSetInteger(0,units.At(i),OBJPROP_YDISTANCE,(int)y_obj-(int)Y_hide); if(StringFind(units.At(i),".Button0",0)>0) ObjectSetString(0,units.At(i),OBJPROP_TEXT,CharToString(MIN_WIN)); } } //--- ChartRedraw(); on_hide=!on_hide; } //+------------------------------------------------------------------ //| CMasterWindows 类 OnChartEvent 事件处理方法 | //+------------------------------------------------------------------ void CMasterWindows::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(on_event // 启用事件处理 && StringFind(sparam,"project1.Exp",0)>=0) { //--- 调用 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); //--- 创建图形对象 if(id==CHARTEVENT_OBJECT_CREATE) { if(StringFind(sparam,"project1.Exp",0)>=0) units.Add(sparam); } //--- 在 Edit STR1 中编辑 [NEW1] if(id==CHARTEVENT_OBJECT_ENDEDIT && StringFind(sparam,".STR1",0)>0) { //--- 事件处理代码 } //--- 编辑 [NEW3] : Plus 按钮 STR3 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".STR3",0)>0 && StringFind(sparam,".Button3",0)>0) { //--- 事件处理代码 } //--- 编辑 [NEW3] : Minus 按钮 STR3 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".STR3",0)>0 && StringFind(sparam,".Button4",0)>0) { //--- 事件处理代码 } //--- 编辑 [NEW4] : Plus 按钮 STR4 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".STR4",0)>0 && StringFind(sparam,".Button3",0)>0) { //--- 事件处理代码 } //--- 编辑 [NEW4] : Minus 按钮 STR4 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".STR4",0)>0 && StringFind(sparam,".Button4",0)>0) { //--- 事件处理代码 } //--- 编辑 [NEW4] : Up 按钮 STR4 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".STR4",0)>0 && StringFind(sparam,".Button5",0)>0) { //--- 事件处理代码 } //--- 编辑 [NEW4] : Down 按钮 STR4 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".STR4",0)>0 && StringFind(sparam,".Button6",0)>0) { //--- 事件处理代码 } //--- [new5] 点击按钮 STR5 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".STR5",0)>0 && StringFind(sparam,".Button",0)>0) { //--- 事件处理代码 } //--- [NEW6] 点击按钮STR6 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".STR6",0)>0 && StringFind(sparam,"(1)",0)>0) { //--- 事件处理代码 } //--- [new6] 点击按钮 STR6 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".STR6",0)>0 && StringFind(sparam,"(2)",0)>0) { //--- 事件处理代码 } //--- 点击按钮 [] STR6 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".STR6",0)>0 && StringFind(sparam,"(3)",0)>0) { //--- 事件处理代码 } //--- 在主窗口中点击了关闭按钮 if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".Button1",0)>0) { ExpertRemove(); } //--- 在主窗口中点击按钮 Hide if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".Button0",0)>0) { Hide(); } } } //--- 主模块声明 CMasterWindows MasterWin; //+------------------------------------------------------------------ //| EA 初始化函数 | //+------------------------------------------------------------------ int OnInit() { //--- 加载主模块 MasterWin.Run(); return(0); } //+------------------------------------------------------------------ //| EA 去初始化函数 | //+------------------------------------------------------------------ void OnDeinit(const int reason) { //--- 反初始化主模块 MasterWin.Deinit(); } //+------------------------------------------------------------------ //| EA 事件函数 | //+------------------------------------------------------------------ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- 调用 OnChartEvent 事件句柄 MasterWin.OnEvent(id,lparam,dparam,sparam); }
启动后,我们看到了以下设计窗口:
图 4.“EA 交易”项目 1 - 类的视觉设计的结果
总结
- 类需要按阶段设计。通过将任务细分到模块,为每一个模块创建单独的类。模块反过来分解为基类或派生类的微模块。
- 不要尝试用内置方法重载基类 - 这些数量应保持在最低限度。
- 由于代码自动生成,即便对“傻瓜”而言,借助视觉设计环境的类的设计也是十分简单的。
附件位置:
- masterwindows.mq5 - ...\MQL5\Experts\
- 其余在文件夹 ...\MQL5\Include\ 中
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/53
![面向初学者的创建具有多个指标缓冲区的指标](https://c.mql5.com/2/0/P2054.png)
![从 MQL4 迁移到 MQL5](https://c.mql5.com/2/0/logo__4.png)
![MetaTrader 5 和 MATLAB 交互](https://c.mql5.com/2/0/matlab.png)