面向對象編程 (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\ 中