DoEasy. 控件 (第 1 部分): 第一步
内容
概述
这篇文章展开一个新的系列,致力于仿照 Windows 窗体样式创建控件。 当然,不可能复现在 MS Visual Studio 中控件列表中包含的所有元素。 我打算利用 MQL5 实现一些开发应用程序 GUI 的最流行元素。
我之所以在前一个主题尚未完成之前就切换到一个新主题,是因为需要使用控件来继续开发之前主题中所涉及的函数库图形对象。 若是没有控件,那么管理就会变得越来越困难。 因此,我将仿照 Windows 窗体样式创建所有可能的控件。 然后,我还会回到之前的主题,那时我们就拥有了全部必要的开发工具。
如果我们在 MS Visual Studio 中打开元素面板,我们将看到控件组列表:
- 全部l Windows 窗体 — 所有可供实施的窗体
- 标准控件
- 容器
- 菜单和工具条
- 数据
- 组件
- 打印
- 对话框
这并非全部 MS Visual Studio 元素面板列表中可用的组。 每个这样的组都包含一大组元素。 所有这些,并非都是函数库所必需的。 我将重点讨论最关键的。
我将从面板元素开始,因为它是窗口元素的基础。 此外,面板是容纳其它控件的容器,而容纳所有元素的面板可以依次放置到父面板当中,而后者也可以是另一个面板中的对象,等等。
我们已经有了基于画布的图形元素对象类,它是基于 CCanvas 类的所有其它图形对象的父类。 窗体类对象基于图形元素。 窗体对象已经有一套操纵和移动它的函数。 面板对象将基于窗体对象来创建。 会在窗体对象里添加新属性,以便实现其功能。
面板能够容纳我在函数库开发说明的当前部分中创建的任何控件。 该面板还能够允许我们在终端中运行的应用程序里实现基本窗口和对话框窗口。
在开发面板类之前,我们应该先改进已经开发的函数库对象类。 毕竟,我还没有完成之前主题的工作。 我打算逐步完成现有函数库对象,并修正检测到的错误。
改进库类
终端版本 3260 的最新更新针对品种和帐户提供了新的属性:
- MQL5: 针对特定品种的报价延迟送达,在 ENUM_SYMBOL_INFO_INTEGER 枚举里加入了 SYMBOL_SUBSCRIPTION_DELAY 值。
它仅用于基于订阅的交易品种。 延迟通常适用于在试用模式下提供的数据。
只有在市场观察中已选择的品种才能请求该属性。 否则,会返回 ERR_MARKET_NOT_SELECTED (4302) 错误。 - MQL5: 在 ENUM_ACCOUNT_INFO_INTEGER 枚举里加入了 ACCOUNT_HEDGE_ALLOWED 属性值 — 启用开立逆向仓位和挂单。 该属性仅用于对冲账户,是为了遵守特定监管要求,根据该要求,一个账户内同一品种不能同时存在互逆的持仓,但允许多笔同向持仓。
如果禁用此选项,则不允许帐户针对同一金融产品持有互逆的持仓和订单。 例如,如果帐户有买入仓位,则用户无法开立卖出仓位,或下达卖出挂单。 如果用户尝试执行此类操作,将返回 TRADE_RETCODE_HEDGE_PROHIBITED 错误。
我们将这些属性添加到品种和函数库帐户对象之中。
在 \MQL5\Include\DoEasy\Data.mqh 里,加入新的消息索引:
//+------------------------------------------------------------------+ //| List of the library's text message indices | //+------------------------------------------------------------------+ enum ENUM_MESSAGES_LIB { MSG_LIB_PARAMS_LIST_BEG=ERR_USER_ERROR_FIRST, // Beginning of the parameter list MSG_LIB_PARAMS_LIST_END, // End of the parameter list MSG_LIB_PROP_NOT_SUPPORTED, // Property not supported MSG_LIB_PROP_NOT_SUPPORTED_MQL4, // Property not supported in MQL4 MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_2155, // Property not supported in MetaTrader 5 versions lower than 2155 MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3245, // Property not supported in MetaTrader 5 versions lower than 3245 MSG_LIB_PROP_NOT_SUPPORTED_POSITION, // Property not supported for position
...
MSG_SYM_PROP_BACKGROUND_COLOR, // Background color of the symbol in Market Watch MSG_SYM_PROP_SUBSCRIPTION_DELAY, // Delay for quotes passed by symbol for instruments working on subscription basis //---
...
MSG_ACC_PROP_FIFO_CLOSE, // Flag of a position closure by FIFO rule only MSG_ACC_PROP_HEDGE_ALLOWED, // Permission to open opposite positions and set pending orders //--- MSG_ACC_PROP_BALANCE, // Account balance
...
MSG_GRAPH_ELEMENT_TYPE_FORM, // Form MSG_GRAPH_ELEMENT_TYPE_WINDOW, // Window MSG_GRAPH_ELEMENT_TYPE_PANEL, // Panel control MSG_GRAPH_OBJ_BELONG_PROGRAM, // Graphical object belongs to a program MSG_GRAPH_OBJ_BELONG_NO_PROGRAM, // Graphical object does not belong to a program //---
以及与新添加的索引对应的文本消息:
{"Свойство не поддерживается в MetaTrader5 версии ниже 2155","The property is not supported in MetaTrader5, build lower than 2155"}, {"Свойство не поддерживается в MetaTrader5 версии ниже 3245","The property is not supported in MetaTrader5, build lower than 3245"}, {"Свойство не поддерживается у позиции","Property not supported for position"},
...
{"Цвет фона символа в Market Watch","Background color of the symbol in Market Watch"}, {"Размер задержки у котировок, передаваемых по символу, для инструментов, работающих по подписке","Delay size for quotes transmitted per symbol for instruments working by subscription"}, {"Максимальный Bid за день","Maximum Bid of the day"},
...
{"Тип торгового сервера","Type of trading server"}, {"Признак закрытия позиций только по правилу FIFO","Sign of closing positions only according to the FIFO rule"}, {"Разрешение на открытие встречных позиций и отложенных ордеров","Permission to open opposite positions and pending orders"}, //--- {"Баланс счета","Account balance"},
...
{"Форма","Form"}, {"Окно","Window"}, {"Элемент управления \"Panel\"","Control element \"Panel\""}, {"Графический объект принадлежит программе","The graphic object belongs to the program"}, {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},
当前文章中创建的任何面板对象都显示文本消息的默认参数。 这些参数将用于面板或其子对象上显示的任何文本,如果面板被视为这些对象的容器,则这些参数也会被附加到面板上。 我们需要为字体设置名称、字号和颜色默认值。
打开 \MQL5\Include\DoEasy\Defines.mqh,并为面板上的这些文本属性添加新的宏替换:
//--- Canvas parameters #define PAUSE_FOR_CANV_UPDATE (16) // Canvas update frequency #define CLR_CANV_NULL (0x00FFFFFF) // Zero for the canvas with the alpha channel #define CLR_FORE_COLOR (C'0x2D,0x43,0x48') // Default color for texts of objects on canvas #define DEF_FONT ("Calibri") // Default font #define DEF_FONT_SIZE (8) // Default font size #define OUTER_AREA_SIZE (16) // Size of one side of the outer area around the form workspace //--- Graphical object parameters
在函数库对象类型列表中加入新的类型:
//+------------------------------------------------------------------+ //| List of library object types | //+------------------------------------------------------------------+ enum ENUM_OBJECT_DE_TYPE { //--- Graphics OBJECT_DE_TYPE_GBASE = COLLECTION_ID_LIST_END+1, // "Base object of all library graphical objects" object type OBJECT_DE_TYPE_GELEMENT, // "Graphical element" object type OBJECT_DE_TYPE_GFORM, // Form object type OBJECT_DE_TYPE_GFORM_CONTROL, // "Form for managing pivot points of graphical object" object type OBJECT_DE_TYPE_GSHADOW, // Shadow object type //--- WinForms OBJECT_DE_TYPE_GWF_PANEL, // WinForms Panel object type //--- Animation OBJECT_DE_TYPE_GFRAME, // "Single animation frame" object type OBJECT_DE_TYPE_GFRAME_TEXT, // "Single text animation frame" object type OBJECT_DE_TYPE_GFRAME_QUAD, // "Single rectangular animation frame" object type OBJECT_DE_TYPE_GFRAME_GEOMETRY, // "Single geometric animation frame" object type OBJECT_DE_TYPE_GANIMATIONS, // "Animations" object type //--- Managing graphical objects
在本节(WinForms)中,我会在创建新对象类型时再添加它们。
在帐户的整数型属性枚举中,添加一个新属性,并将整数型对象属性的数量从 11 增加到 12:
//+------------------------------------------------------------------+ //| Account integer properties | //+------------------------------------------------------------------+ enum ENUM_ACCOUNT_PROP_INTEGER { ... ACCOUNT_PROP_FIFO_CLOSE, // Flag of a position closure by FIFO rule only ACCOUNT_PROP_HEDGE_ALLOWED // Permission to open opposite positions and set pending orders }; #define ACCOUNT_PROP_INTEGER_TOTAL (12) // Total number of integer properties #define ACCOUNT_PROP_INTEGER_SKIP (0) // Number of integer account properties not used in sorting //+------------------------------------------------------------------+
在可能的帐户排序条件列表中加入新的属性:
//+------------------------------------------------------------------+ //| Possible account sorting criteria | //+------------------------------------------------------------------+ #define FIRST_ACC_DBL_PROP (ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_INTEGER_SKIP) #define FIRST_ACC_STR_PROP (ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_INTEGER_SKIP+ACCOUNT_PROP_DOUBLE_TOTAL-ACCOUNT_PROP_DOUBLE_SKIP) enum ENUM_SORT_ACCOUNT_MODE { ... SORT_BY_ACCOUNT_FIFO_CLOSE, // Sort by the flag of a position closure by FIFO rule only SORT_BY_ACCOUNT_HEDGE_ALLOWED, // Sort by permission to open opposite positions and set pending orders //--- Sort by real properties SORT_BY_ACCOUNT_BALANCE = FIRST_ACC_DBL_PROP, // Sort by an account balance in the deposit currency SORT_BY_ACCOUNT_CREDIT, // Sort by credit in a deposit currency ... SORT_BY_ACCOUNT_COMPANY // Sort by a name of a company serving an account }; //+------------------------------------------------------------------+
在品种的整数型属性枚举中,添加一个新属性,并将整数型属性的数量从 40 增加到 41:
//+------------------------------------------------------------------+ //| Symbol integer properties | //+------------------------------------------------------------------+ enum ENUM_SYMBOL_PROP_INTEGER { //--- ... SYMBOL_PROP_OPTION_MODE, // Option type (from the ENUM_SYMBOL_OPTION_MODE enumeration) SYMBOL_PROP_OPTION_RIGHT, // Option right (Call/Put) (from the ENUM_SYMBOL_OPTION_RIGHT enumeration) SYMBOL_PROP_SUBSCRIPTION_DELAY, // Delay for quotes passed by symbol for instruments working on subscription basis //--- skipped property SYMBOL_PROP_BACKGROUND_COLOR // The color of the background used for the symbol in Market Watch }; #define SYMBOL_PROP_INTEGER_TOTAL (41) // Total number of integer properties #define SYMBOL_PROP_INTEGER_SKIP (1) // Number of symbol integer properties not used in sorting //+------------------------------------------------------------------+
在可能的品种排序标准枚举中,加入按照新的属性排序:
//+------------------------------------------------------------------+ //| Possible symbol sorting criteria | //+------------------------------------------------------------------+ #define FIRST_SYM_DBL_PROP (SYMBOL_PROP_INTEGER_TOTAL-SYMBOL_PROP_INTEGER_SKIP) #define FIRST_SYM_STR_PROP (SYMBOL_PROP_INTEGER_TOTAL-SYMBOL_PROP_INTEGER_SKIP+SYMBOL_PROP_DOUBLE_TOTAL-SYMBOL_PROP_DOUBLE_SKIP) enum ENUM_SORT_SYMBOLS_MODE { ... SORT_BY_SYMBOL_OPTION_MODE, // Sort by option type (from the ENUM_SYMBOL_OPTION_MODE enumeration) SORT_BY_SYMBOL_OPTION_RIGHT, // Sort by option right (Call/Put) (from the ENUM_SYMBOL_OPTION_RIGHT enumeration) SORT_BY_SYMBOL_SUBSCRIPTION_DELAY, // Sort by delay for quotes passed by symbol for instruments working on subscription basis
在图形元素类型列表中添加新的元素类型:
//+------------------------------------------------------------------+ //| The list of graphical element types | //+------------------------------------------------------------------+ enum ENUM_GRAPH_ELEMENT_TYPE { GRAPH_ELEMENT_TYPE_STANDARD, // Standard graphical object GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED, // Extended standard graphical object GRAPH_ELEMENT_TYPE_ELEMENT, // Element GRAPH_ELEMENT_TYPE_SHADOW_OBJ, // Shadow object GRAPH_ELEMENT_TYPE_FORM, // Form GRAPH_ELEMENT_TYPE_WINDOW, // Window //--- WinForms GRAPH_ELEMENT_TYPE_PANEL, // Windows Forms Panel }; //+------------------------------------------------------------------+
创建每个后续控件时,其类型将输入该枚举的子部分(WinForms)。
如果将另一个对象(大于容器面板)添加到面板对象,并且允许面板自动更改其大小,那么会有两个大小变更选项:
- 仅增加面板大小
- 增加和减小面板大小
在第一种情况下,若面板边缘不能囊括其中的对象,则面板边界将被扩大,以便完全适合对象大小。 在第二种情况下,除了上述操作外,若面板边缘大于内部的对象,它还允许减少面板的边线大小。
在可能的图形对象排序条件枚举之后,添加一个新的枚举,设置自动更改界面元素大小的模式:
//+------------------------------------------------------------------+ //| Mode of automatic interface element resizing | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_AUTO_SIZE_MODE { CANV_ELEMENT_AUTO_SIZE_MODE_GROW, // Increase only CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK, // Increase and decrease }; //+------------------------------------------------------------------+
当把对象放置在面板内时,对象可以附着到其容器的任何一侧 — 顶部、底部、右侧和左侧。 在这种情况下,最近的一侧“粘住”到容器对象的相应一侧,且附着对象的维度会被拉伸到与容器垂直一侧相配。 例如,如果对象附着到其容器的顶侧,则对象的顶边将拉到容器的顶边,而对象的左侧和右侧将拉伸到容器的相应侧边。 对象高度保持不变。 附着到容器其它侧边的操作方式与此类似。
在自动更改大小模式枚举之后添加新枚举:
//+------------------------------------------------------------------+ //| Control borders bound to the container | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_DOCK_MODE { CANV_ELEMENT_DOCK_MODE_TOP, // Attaching to the top and stretching along the container width CANV_ELEMENT_DOCK_MODE_BOTTOM, // Attaching to the bottom and stretching along the container width CANV_ELEMENT_DOCK_MODE_LEFT, // Attaching to the left and stretching along the container height CANV_ELEMENT_DOCK_MODE_RIGHT, // Attaching to the right and stretching along the container height CANV_ELEMENT_DOCK_MODE_FILL, // Stretching along the entire container width and height CANV_ELEMENT_DOCK_MODE_NONE, // Attached to the specified coordinates, size does not change }; //+------------------------------------------------------------------+
除了上述四种将对象附着到容器的方式以外,还有两种方式:填充(对象大小调整为容器大小),和不附着(对象仅附着到其容器内的指定坐标,且保持其尺寸不变)。
如果控件拥有与用户交互的功能,则在某些条件下(例如,按钮处于非活动状态),此类对象可能会被认定无法进行交互。 添加新的图形元素属性,来指示与元素交互的可能性。
在图形元素整数型属性的枚举中,添加一个新属性,并将对象整数型属性的数量从 24 增加到 25:
//+------------------------------------------------------------------+ //| Integer properties of the graphical element on the canvas | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_PROP_INTEGER { ... CANV_ELEMENT_PROP_ZORDER, // Priority of a graphical object for receiving the event of clicking on a chart CANV_ELEMENT_PROP_ENABLED, // Element availability flag }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (25) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting //+------------------------------------------------------------------+
在基于画布的图形元素排序的可能条件枚举中,加入按照新属性排序:
//+------------------------------------------------------------------+ //| Possible sorting criteria of graphical elements on the canvas | //+------------------------------------------------------------------+ #define FIRST_CANV_ELEMENT_DBL_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP) #define FIRST_CANV_ELEMENT_STR_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP) enum ENUM_SORT_CANV_ELEMENT_MODE { ... SORT_BY_CANV_ELEMENT_ZORDER, // Sort by the priority of a graphical object for receiving the event of clicking on a chart SORT_BY_CANV_ELEMENT_ENABLED, // Sort by the element availability flag ... }; //+------------------------------------------------------------------+
由于现在品种和帐户有了新的属性,因此需要改进对象类。
打开 \MQL5\Include\DoEasy\Objects\Accounts\Account.mqh,并改进账户对象类。
往对象属性结构里加入新的整数型属性:
//+------------------------------------------------------------------+ //| Account class | //+------------------------------------------------------------------+ class CAccount : public CBaseObjExt { private: struct SData { //--- Account integer properties ... bool fifo_close; // ACCOUNT_FIFO_CLOSE (The flag indicating that positions can be closed only by the FIFO rule) bool hedge_allowed; // ACCOUNT_HEDGE_ALLOWED (Permission to open opposite positions and set pending orders) ... }; SData m_struct_obj; // Account object structure uchar m_uchar_array[]; // uchar array of the account object structure //--- Object properties
在简化访问帐户对象属性的方法模块里添加新方法:
//+------------------------------------------------------------------+ //| Methods of a simplified access to the account object properties | //+------------------------------------------------------------------+ //--- Return the account's integer properties ... bool FIFOClose(void) const { return (bool)this.GetProperty(ACCOUNT_PROP_FIFO_CLOSE); } bool IsHedge(void) const { return this.MarginMode()==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING; } bool HedgeAllowed(void) const { return (bool)this.GetProperty(ACCOUNT_PROP_HEDGE_ALLOWED); } ...
该方法简单地返回存储在对象属性数组中的数值。
在类的构造函数里把数值添加到对象属性数组之中:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CAccount::CAccount(void) { this.m_type=OBJECT_DE_TYPE_ACCOUNT; //--- Initialize control data this.SetControlDataArraySizeLong(ACCOUNT_PROP_INTEGER_TOTAL); this.SetControlDataArraySizeDouble(ACCOUNT_PROP_DOUBLE_TOTAL); this.ResetChangesParams(); this.ResetControlsParams(); ... this.m_long_prop[ACCOUNT_PROP_SERVER_TYPE] = (::TerminalInfoString(TERMINAL_NAME)=="MetaTrader 5" ? 5 : 4); this.m_long_prop[ACCOUNT_PROP_FIFO_CLOSE] = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<2155 ? false : ::AccountInfoInteger(ACCOUNT_FIFO_CLOSE) #else false #endif ); this.m_long_prop[ACCOUNT_PROP_HEDGE_ALLOWED] = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<3245 ? false : ::AccountInfoInteger(ACCOUNT_HEDGE_ALLOWED) #else false #endif ); ... //--- Update the base object data and search for changes CBaseObjExt::Refresh(); } //+-------------------------------------------------------------------+
如果 MQL5 的版本低于 3245,则该属性不存在。 设为 false。 如果终端的版本为 3245 或更高,那么可从帐户新属性中获取该值,并在对象整数型属性数组设置其值。 若是 MQL4 的情况,则其始终设置为 false,因为它没有这个、以及许多其它属性。
在更新所有帐户数据的方法中,以完全相同的方式为新对象属性设置对应值:
//+------------------------------------------------------------------+ //| Update all account data | //+------------------------------------------------------------------+ void CAccount::Refresh(void) { //--- Initialize event data this.m_is_event=false; this.m_hash_sum=0; ... this.m_long_prop[ACCOUNT_PROP_FIFO_CLOSE] = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<2155 ? false : ::AccountInfoInteger(ACCOUNT_FIFO_CLOSE) #else false #endif ); this.m_long_prop[ACCOUNT_PROP_HEDGE_ALLOWED] = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<3245 ? false : ::AccountInfoInteger(ACCOUNT_HEDGE_ALLOWED) #else false #endif ); ... CBaseObjExt::Refresh(); this.CheckEvents(); } //+------------------------------------------------------------------+
在创建帐户对象结构的方法中,在结构的两个字段里添加输入数据:
//+------------------------------------------------------------------+ //| Create the account object structure | //+------------------------------------------------------------------+ bool CAccount::ObjectToStruct(void) { //--- Save integer properties ... this.m_struct_obj.server_type=(int)this.ServerType(); this.m_struct_obj.fifo_close=this.FIFOClose(); this.m_struct_obj.hedge_allowed=this.HedgeAllowed(); ... //--- Save the structure to the uchar array ::ResetLastError(); if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY),(string)::GetLastError()); return false; } return true; } //+------------------------------------------------------------------+
在此,我们往对象结构的整数型字段里加入了一个新属性,并设置在 2155 版本里加入的账户属性 FIFOClose。
在结构外创建帐户对象的方法中,加入从结构字段取值,并设置到对象新属性:
//+------------------------------------------------------------------+ //| Create the account object from the structure | //+------------------------------------------------------------------+ void CAccount::StructToObject(void) { //--- Save integer properties ... this.m_long_prop[ACCOUNT_PROP_FIFO_CLOSE] = this.m_struct_obj.fifo_close; this.m_long_prop[ACCOUNT_PROP_HEDGE_ALLOWED] = this.m_struct_obj.hedge_allowed; ... } //+------------------------------------------------------------------+
在返回帐户整数型属性描述的方法中,添加显示新属性说明的代码模块:
//+------------------------------------------------------------------+ //| Return the description of the account integer property | //+------------------------------------------------------------------+ string CAccount::GetPropertyDescription(ENUM_ACCOUNT_PROP_INTEGER property) { return ( ... property==ACCOUNT_PROP_FIFO_CLOSE ? CMessage::Text(MSG_ACC_PROP_FIFO_CLOSE)+": "+ (this.GetProperty(property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO)) : property==ACCOUNT_PROP_HEDGE_ALLOWED ? CMessage::Text(MSG_ACC_PROP_HEDGE_ALLOWED)+": "+ (this.GetProperty(property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO)) : "" ); } //+------------------------------------------------------------------+
在 \MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh 中的品种对象文件中完成类似的改进 。
在类的受保护部分中,声明返回新符号属性值的方法:
protected: //--- Protected parametric constructor CSymbol(ENUM_SYMBOL_STATUS symbol_status,const string name,const int index); //--- Get and return integer properties of a selected symbol from its parameters ... long SymbolCalcMode(void) const; long SymbolSwapMode(void) const; long SymbolSubscriptionDelay(void) const; long SymbolDigitsLot(void); int SymbolDigitsBySwap(void); ... //--- Search for a symbol and return the flag indicating its presence on the server bool Exist(void) const; public:
在简化访问品种对象属性方法模块的公开部分,加入一个返回新属性值的方法:
//+------------------------------------------------------------------+ //| Methods for a simplified access to symbol object properties | //+------------------------------------------------------------------+ //--- Integer properties ... ENUM_SYMBOL_OPTION_MODE OptionMode(void) const { return (ENUM_SYMBOL_OPTION_MODE)this.GetProperty(SYMBOL_PROP_OPTION_MODE); } ENUM_SYMBOL_OPTION_RIGHT OptionRight(void) const { return (ENUM_SYMBOL_OPTION_RIGHT)this.GetProperty(SYMBOL_PROP_OPTION_RIGHT); } long SubscriptionDelay(void) const { return this.GetProperty(SYMBOL_PROP_SUBSCRIPTION_DELAY); } //--- Real properties
在此,我们简单地调用 GetProperty() 方法返回品种对象整数型属性数组中的数值集合。
在闭合参数的构造函数中,为对象整数型属性数组里设置一个新属性:
//+------------------------------------------------------------------+ //| Closed parametric constructor | //+------------------------------------------------------------------+ CSymbol::CSymbol(ENUM_SYMBOL_STATUS symbol_status,const string name,const int index) { //--- Save integer properties ... this.m_long_prop[SYMBOL_PROP_BOOKDEPTH_STATE] = this.m_book_subscribed; this.m_long_prop[SYMBOL_PROP_SUBSCRIPTION_DELAY] = this.SymbolSubscriptionDelay(); ... //--- Initializing default values of a trading object this.m_trade.Init(this.Name(),0,this.LotsMin(),5,0,0,false,this.GetCorrectTypeFilling(),this.GetCorrectTypeExpiration(),LOG_LEVEL_ERROR_MSG); } //+------------------------------------------------------------------+
该方法基于所传递的订阅品种,返回其报价的延迟大小:
//+------------------------------------------------------------------+ //| Return the delay size for quotes passed by symbol | //| in case of subscription-based symbols | //+------------------------------------------------------------------+ long CSymbol::SymbolSubscriptionDelay(void) const { return ( #ifdef __MQL5__ (::TerminalInfoInteger(TERMINAL_BUILD)>=3245 ? ::SymbolInfoInteger(this.m_name,SYMBOL_SUBSCRIPTION_DELAY) : 0) #else 0 #endif ); } //+------------------------------------------------------------------+
此处,针对 MQL5 的情况,如果终端版本为 3245 或更高,则返回新品种的属性值,否则 — 返回零。
若是 MQL4 的情况,始终返回零o。 因为它里面没有这个属性。
在返回品种整数型属性描述的方法中,添加一个返回新属性描述的代码模块:
//+------------------------------------------------------------------+ //| Return the description of the symbol integer property | //+------------------------------------------------------------------+ string CSymbol::GetPropertyDescription(ENUM_SYMBOL_PROP_INTEGER property) { return ( ... property==SYMBOL_PROP_BACKGROUND_COLOR ? CMessage::Text(MSG_SYM_PROP_BACKGROUND_COLOR)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : #ifdef __MQL5__ (this.GetProperty(property)==CLR_MW_DEFAULT || this.GetProperty(property)==CLR_NONE ? ": ("+CMessage::Text(MSG_LIB_PROP_EMPTY)+")" : ": "+::ColorToString((color)this.GetProperty(property),true)) #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif ) : property==SYMBOL_PROP_SUBSCRIPTION_DELAY ? CMessage::Text(MSG_SYM_PROP_SUBSCRIPTION_DELAY)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : #ifdef __MQL5__ (::TerminalInfoInteger(TERMINAL_BUILD)<3245 ? ": ("+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3245)+")" : ": "+(string)this.GetProperty(property)) #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif ) : "" ); } //+------------------------------------------------------------------+
针对位于 \MQL5\Include\DoEasy\GraphINI.mqh 中 GUI 元素的配色方案,加入文本颜色值,将配色方案中的参数数量从 4 提升到 5,并将文本颜色值添加到配色方案值数组当中:
//+------------------------------------------------------------------+ //| List of indices of color scheme parameters | //+------------------------------------------------------------------+ enum ENUM_COLOR_THEME_COLORS { COLOR_THEME_COLOR_FORM_BG, // Form background color COLOR_THEME_COLOR_FORM_FRAME, // Form frame color COLOR_THEME_COLOR_FORM_RECT_OUTER, // Form outline rectangle color COLOR_THEME_COLOR_FORM_SHADOW, // Form shadow color COLOR_THEME_COLOR_FORM_TEXT, // Form text color }; #define TOTAL_COLOR_THEME_COLORS (5) // Number of parameters in the color theme //+------------------------------------------------------------------+ //| The array containing color schemes | //+------------------------------------------------------------------+ color array_color_themes[TOTAL_COLOR_THEMES][TOTAL_COLOR_THEME_COLORS]= { //--- Parameters of the "Blue steel" color scheme { C'134,160,181', // Form background color C'134,160,181', // Form frame color clrDimGray, // Form outline rectangle color clrGray, // Form shadow color C'0x3E,0x3E,0x3E', // Form text color }, //--- Parameters of the "Light cyan gray" color scheme { C'181,196,196', // Form background color C'181,196,196', // Form frame color clrGray, // Form outline rectangle color clrGray, // Form shadow color C'0x3E,0x3E,0x3E', // Form text color }, }; //+------------------------------------------------------------------+
在框架样式枚举中,添加代表框架缺失的字段:
//+------------------------------------------------------------------+ //| Frame styles | //+------------------------------------------------------------------+ enum ENUM_FRAME_STYLE { FRAME_STYLE_NONE, // No frame FRAME_STYLE_SIMPLE, // Simple frame FRAME_STYLE_FLAT, // Flat frame FRAME_STYLE_BEVEL, // Embossed (convex) FRAME_STYLE_STAMP, // Embossed (concave) }; //+------------------------------------------------------------------+
在 \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh 里改进所有函数库图形对象的基准对象类.
在指定当前图表 ID 时,我们要能够在自定义程序中设置 0 或 NULL,替代指定数字 ID 值,或传递 ChartID() 函数,此外针对传递给 SetChartI() 方法的值添加验证:
public: //--- Return the prefix name string NamePrefix(void) const { return this.m_name_prefix; } //--- Set the values of the class variables void SetObjectID(const long value) { this.m_object_id=value; } void SetBelong(const ENUM_GRAPH_OBJ_BELONG belong){ this.m_belong=belong; } void SetTypeGraphObject(const ENUM_OBJECT obj) { this.m_type_graph_obj=obj; } void SetTypeElement(const ENUM_GRAPH_ELEMENT_TYPE type) { this.m_type_element=type; } void SetSpecies(const ENUM_GRAPH_OBJ_SPECIES species){ this.m_species=species; } void SetGroup(const int group) { this.m_group=group; } void SetName(const string name) { this.m_name=name; } void SetDigits(const int value) { this.m_digits=value; } void SetChartID(const long chart_id) { this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id); } //--- Set the "Background object" flag
我们在此处检查那个传递给该方法的数值。 如果为 0 或为NULL,则将当前图表 ID 分配给该变量。 否则,取传递给方法的值赋值给它。
在返回图形元素类型描述的方法中,添加并返回面板对象描述:
//+------------------------------------------------------------------+ //| Return the description of the graphical element type | //+------------------------------------------------------------------+ string CGBaseObj::TypeElementDescription(void) { return ( this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_ELEMENT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_SHADOW_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WINDOW ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW) : //--- this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_PANEL) : "Unknown" ); } //+------------------------------------------------------------------+
在 \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh 中的图形对象类里,即在对象结构中,为元素可用性属性添加一个新字段:
//+------------------------------------------------------------------+ //| Class of the graphical element object | //+------------------------------------------------------------------+ class CGCnvElement : public CGBaseObj { protected: CCanvas m_canvas; // CCanvas class object CPause m_pause; // Pause class object bool m_shadow; // Shadow presence color m_chart_color_bg; // Chart background color uint m_duplicate_res[]; // Array for storing resource data copy //--- Create (1) the object structure and (2) the object from the structure virtual bool ObjectToStruct(void); virtual void StructToObject(void); private: struct SData { //--- Object integer properties ... int coord_act_bottom; // Bottom border of the element active area long zorder; // Priority of a graphical object for receiving the event of clicking on a chart bool enabled; // Element availability flag //--- Object real properties //--- Object string properties uchar name_obj[64]; // Graphical element object name uchar name_res[64]; // Graphical resource name }; SData m_struct_obj; // Object structure uchar m_uchar_array[]; // uchar array of the object structure
在方法模块中添加新方法,设置和返回元素可用性属性的,简化对对象属性的访问:
//+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ ... //--- Set (1) object movability, (2) activity, (3) interaction, //--- (4) element ID, (5) element index in the list, (6) availability and (7) shadow flag void SetMovable(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,flag); } void SetActive(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,flag); } void SetInteraction(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,flag); } void SetID(const int id) { this.SetProperty(CANV_ELEMENT_PROP_ID,id); } void SetNumber(const int number) { this.SetProperty(CANV_ELEMENT_PROP_NUM,number); } void SetEnabled(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_ENABLED,flag); } void SetShadow(const bool flag) { this.m_shadow=flag; } ... //--- Return the (1) element movability, (2) activity, (3) interaction and (4) availability flag bool Movable(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE); } bool Active(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE); } bool Interaction(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_INTERACTION); } bool Enabled(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_ENABLED); } //--- Return (1) the object name, (2) the graphical resource name, (3) the chart ID and (4) the chart subwindow index
在画布清除方法之一当中,删除默认标志值:
//+------------------------------------------------------------------+ //| The methods of filling, clearing and updating raster data | //+------------------------------------------------------------------+ //--- Clear the element filling it with color and opacity void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Clear the element completely void Erase(const bool redraw=false); //--- Update the element void Update(const bool redraw=false) { this.m_canvas.Update(redraw); }
之前,该方法如下所示:
void Erase(color &colors[],const uchar opacity,const bool vgradient=true,const bool cycle=false,const bool redraw=false);
由于编译器无法选择正确的重载方法,因此无法调用该方法。
在参数化构造函数中,针对传递的图表 ID 值添加验证,并设置元素可用性标志,以及默认字体值:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int element_id, const int element_num, const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable=true, const bool activity=true, const bool redraw=false) : m_shadow(false) { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND); this.m_name=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id); this.m_subwindow=wnd_num; this.m_type_element=element_type; this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.m_text_anchor=0; this.m_text_x=0; this.m_text_y=0; this.m_color_bg=colour; this.m_opacity=opacity; if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw)) { ... this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,activity); // Element activity flag this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,false); // Flag of interaction with the outside environment this.SetProperty(CANV_ELEMENT_PROP_ENABLED,true); // Element availability flag this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge()); // Element right border ... } //+------------------------------------------------------------------+
在受保护的构造函数中执行相同操作:
//+------------------------------------------------------------------+ //| Protected constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h) : m_shadow(false) { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND); this.m_name=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id); this.m_subwindow=wnd_num; this.m_type_element=element_type; this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.m_text_anchor=0; this.m_text_x=0; this.m_text_y=0; this.m_color_bg=CLR_CANV_NULL; this.m_opacity=0; if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,this.m_color_bg,this.m_opacity,false)) { ... this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,false); // Element activity flag this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,false); // Flag of interaction with the outside environment this.SetProperty(CANV_ELEMENT_PROP_ENABLED,true); // Element availability flag this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge()); // Element right border ... } //+------------------------------------------------------------------+
在创建对象结构的方法中,加入采用新元素可用性标志来填充新结构字段:
//+------------------------------------------------------------------+ //| Create the object structure | //+------------------------------------------------------------------+ bool CGCnvElement::ObjectToStruct(void) { //--- Save integer properties ... this.m_struct_obj.active=(bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE); // Element activity flag this.m_struct_obj.interaction=(bool)this.GetProperty(CANV_ELEMENT_PROP_INTERACTION); // Flag of interaction with the outside environment this.m_struct_obj.enabled=(bool)this.GetProperty(CANV_ELEMENT_PROP_ENABLED); // Element availability flag this.m_struct_obj.coord_act_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_X); // X coordinate of the element active area this.m_struct_obj.coord_act_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y); // Y coordinate of the element active area ... return true; } //+------------------------------------------------------------------+
在结构外部创建对象的方法中,从相应的结构字段取值,填写对象可用性属性项:
//+------------------------------------------------------------------+ //| Create the object from the structure | //+------------------------------------------------------------------+ void CGCnvElement::StructToObject(void) { //--- Save integer properties ... this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,this.m_struct_obj.active); // Element activity flag this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,this.m_struct_obj.interaction); // Flag of interaction with the outside environment this.SetProperty(CANV_ELEMENT_PROP_ENABLED,this.m_struct_obj.enabled); // Element availability flag this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.m_struct_obj.coord_act_x); // X coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.m_struct_obj.coord_act_y); // Y coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.m_struct_obj.coord_act_right); // Right border of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.m_struct_obj.coord_act_bottom); // Bottom border of the element active area this.m_color_bg=this.m_struct_obj.color_bg; // Element background color this.m_opacity=this.m_struct_obj.opacity; // Element opacity this.m_zorder=this.m_struct_obj.zorder; // Priority of a graphical object for receiving the on-chart mouse click event ... //--- Save string properties this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj));// Graphical element object name this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res));// Graphical resource name } //+------------------------------------------------------------------+
在创建图形元素对象的方法中,也要添加针对传递的图表 ID 值进行验证:
//+------------------------------------------------------------------+ //| Create the graphical element object | //+------------------------------------------------------------------+ bool CGCnvElement::Create(const long chart_id, // Chart ID const int wnd_num, // Chart subwindow const string name, // Element name const int x, // X coordinate const int y, // Y coordinate const int w, // Width const int h, // Height const color colour, // Background color const uchar opacity, // Opacity const bool redraw=false) // Flag indicating the need to redraw { ::ResetLastError(); if(this.m_canvas.CreateBitmapLabel((chart_id==NULL ? ::ChartID() : chart_id),wnd_num,name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE)) { this.Erase(CLR_CANV_NULL); this.m_canvas.Update(redraw); this.m_shift_y=(int)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_WINDOW_YDISTANCE,wnd_num); return true; } CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //+------------------------------------------------------------------+
我们来改进 \MQL5\Include\DoEasy\Objects\Graph\Form.mqh 中的窗体对象类。
私密变量初始化方法
//+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CForm : public CGCnvElement { private: CArrayObj m_list_elements; // List of attached elements CAnimations *m_animations; // Pointer to the animation object CShadowObj *m_shadow_obj; // Pointer to the shadow object CMouseState m_mouse; // "Mouse status" class object ENUM_MOUSE_FORM_STATE m_mouse_form_state; // Mouse status relative to the form ushort m_mouse_state_flags; // Mouse status flags color m_color_frame; // Form frame color int m_frame_width_left; // Form frame width to the left int m_frame_width_right; // Form frame width to the right int m_frame_width_top; // Form frame width at the top int m_frame_width_bottom; // Form frame width at the bottom int m_offset_x; // Offset of the X coordinate relative to the cursor int m_offset_y; // Offset of the Y coordinate relative to the cursor //--- Initialize the variables void Initialize(void); //--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames void ResetArrayFrameT(void); void ResetArrayFrameQ(void); void ResetArrayFrameG(void);
将其移到类的受保护的部分,因为衍生子对象中需要该方法,并声明一个逆初始化类对象的新方法:
//--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames void ResetArrayFrameT(void); void ResetArrayFrameQ(void); void ResetArrayFrameG(void); //--- Return the name of the dependent object string CreateNameDependentObject(const string base_name) const { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name; } //--- Create a new graphical object CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); //--- Create a shadow object void CreateShadowObj(const color colour,const uchar opacity); protected: //--- Initialize the variables void Initialize(void); void Deinitialize(void); public: //--- Return (1) the mouse status relative to the form, as well as (2) X and (3) Y coordinate of the cursor
把返回附着对象列表的 GetList() 方法重命名为 GetListElements(),这更符合其功能:
//--- Return (1) the list of attached objects and (2) the shadow object CForm *GetObject(void) { return &this; } CArrayObj *GetListElements(void) { return &this.m_list_elements; } CGCnvElement *GetShadowObj(void) { return this.m_shadow_obj; } //--- Return the pointer to (1) the animation object, the list of (2) text and (3) rectangular animation frames
在类的公开部分中,声明将新的附加元素加入到窗体已附加元素列表中的方法:
//--- Create a new attached element bool CreateNewElement(const int element_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); //--- Add a new attached element bool AddNewElement(CGCnvElement *obj,const int x,const int y); //--- Draw an object shadow void DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=4);
从类的析构函数中,把移除所有已用动态类对象的代码模块移出
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CForm::~CForm() { if(this.m_shadow_obj!=NULL) delete this.m_shadow_obj; if(this.m_animations!=NULL) delete this.m_animations; } //+------------------------------------------------------------------+
移至新的逆初始化方法里:
//+------------------------------------------------------------------+ //| Deinitialize the variables | //+------------------------------------------------------------------+ void CForm::Deinitialize(void) { if(this.m_shadow_obj!=NULL) delete this.m_shadow_obj; if(this.m_animations!=NULL) delete this.m_animations; } //+------------------------------------------------------------------+
在析构函数中调用以下方法:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CForm::~CForm() { this.Deinitialize(); } //+------------------------------------------------------------------+
这样就令我们能够从继承的类中删除不必要的父类动态对象。
该方法将新附着元素添加到已附着对象元素列表:
//+------------------------------------------------------------------+ //| Add a new attached element | //+------------------------------------------------------------------+ bool CForm::AddNewElement(CGCnvElement *obj,const int x,const int y) { if(obj==NULL) return false; this.m_list_elements.Sort(SORT_BY_CANV_ELEMENT_NAME_OBJ); int index=this.m_list_elements.Search(obj); if(index>WRONG_VALUE) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_OBJ_ALREADY_IN_LIST),": ",obj.NameObj()); return false; } if(!this.m_list_elements.Add(obj)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST),": ",obj.NameObj()); return false; } return true; } //+------------------------------------------------------------------+
该方法接收指向要添加到已附着对象列表中的对象指针。
按指定对象名称对元素列表进行排序,并在列表中搜索该类对象。
如果列表中没有同名对象,则通知,并返回 false。
如果未能将对象放置到已附着对象列表当中,则通知,并返回 false。
结果则为,返回 true。
该方法创建新的附加元素,现在调用将所创建对象添加到列表的方法:
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CForm::CreateNewElement(const int element_num, const string element_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { CGCnvElement *obj=this.CreateNewGObject(GRAPH_ELEMENT_TYPE_ELEMENT,element_num,element_name,x,y,w,h,colour,opacity,movable,activity); if(obj==NULL) return false; if(!this.AddNewElement(obj,x,y)) { delete obj; return false; } return true; } //+------------------------------------------------------------------+
之前,我在这个方法中曾将新创建的对象加入到列表当中。 这不太合理,因为我们能够把来自其它程序部件中的图形元素加到已附加对象列表当中(不仅仅是在创建对象时)。
在创建阴影对象的方法中,可移动性标志设置为 true。 这令阴影对象可移动。 我相信,这种行为是不正确的。 取而代之,属性值应继承自构建的阴影对象。 我们来修复这个问题:
//+------------------------------------------------------------------+ //| Create the shadow object | //+------------------------------------------------------------------+ void CForm::CreateShadowObj(const color colour,const uchar opacity) { //--- If the shadow flag is disabled or the shadow object already exists, exit if(!this.m_shadow || this.m_shadow_obj!=NULL) return; //--- Calculate the shadow object coordinates according to the offset from the top and left int x=this.CoordX()-OUTER_AREA_SIZE; int y=this.CoordY()-OUTER_AREA_SIZE; //--- Calculate the width and height in accordance with the top, bottom, left and right offsets int w=this.Width()+OUTER_AREA_SIZE*2; int h=this.Height()+OUTER_AREA_SIZE*2; //--- Create a new shadow object and set the pointer to it in the variable this.m_shadow_obj=new CShadowObj(this.ChartID(),this.SubWindow(),this.CreateNameDependentObject("Shadow"),x,y,w,h); if(this.m_shadow_obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ)); return; } //--- Set the properties for the created shadow object this.m_shadow_obj.SetID(this.ID()); this.m_shadow_obj.SetNumber(-1); this.m_shadow_obj.SetOpacityShadow(opacity); this.m_shadow_obj.SetColorShadow(colour); this.m_shadow_obj.SetMovable(this.Movable()); this.m_shadow_obj.SetActive(false); this.m_shadow_obj.SetVisible(false,false); //--- Move the form object to the foreground this.BringToTop(); } //+------------------------------------------------------------------+
所有准备阶段均已完成。
WinForms 面板对象类
面板对象将派生自窗体对象类。 换言之,它将拥有完整的窗体功能和属性。 此外,我将为它添加了新的属性和功能。 面板将能够在其中放置其他对象,以及更改其大小来适应内容,并在内容超出面板时启用自动滚动。
在本文中,我只准备制作面板对象 — 我将定义它的所有属性,并创建设置和返回它们的方法。 在后续的文章中,我将逐步增加面板对象的所有功能。 在此,我只能用其构造函数来创建面板对象。
针对所有 WinForms 管理元素,定义一个新的函数库目录。
创建一个新文件夹 \MQL5\Include\DoEasy\Objects\Graph\WForms\,子文件夹的命名,则如本文开头所定义的 MS Visual Studio 控件组数量:
- \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\
- \MQL5\Include\DoEasy\Objects\Graph\WForms\Components\
- \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\
- \MQL5\Include\DoEasy\Objects\Graph\WForms\Data\
- \MQL5\Include\DoEasy\Objects\Graph\WForms\Dialogs\
- \MQL5\Include\DoEasy\Objects\Graph\WForms\Menu & Toolbars\
- \MQL5\Include\DoEasy\Objects\Graph\WForms\Printing
由于面板是其它对象的容器,因此对象类文件位于相应的文件夹 \MQL5\Include\DoEasy\objects\Graph\WForms \Containers \ 之中。
在指定的文件中,创建一个内含 CPanel 类的新文件 Panel.mqh,其派生自 CForm,故应包含其文件在内:
//+------------------------------------------------------------------+ //| Panel.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Form.mqh" //+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+ class CPanel : public CForm { }
在类的私密部分中,声明所有必需的变量和数组:
//+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+ class CPanel : public CForm { private: color m_fore_color; // Default text color for all panel objects ENUM_FRAME_STYLE m_border_style; // Panel frame style bool m_autoscroll; // Auto scrollbar flag int m_autoscroll_margin[2]; // Array of fields around the control during an auto scroll bool m_autosize; // Flag of the element auto resizing depending on the content ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode; // Mode of the element auto resizing depending on the content ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode; // Mode of binding element borders to the container int m_margin[4]; // Array of gaps of all sides between the fields of the current and adjacent controls int m_padding[4]; // Array of gaps of all sides inside controls public:
为了掌握术语 Margin、Padding 和 AutoSize,我们需研究一下来自 MS Windows Forms .NET Framework 4.X 帮助中的以下示例:
... 其中三个最重要的属性是 Margin、Padding 和 AutoSize 属性,它们存在于所有 Windows 窗体控件上。
Margin 属性定义控件周围的空间,令控件与控件边界保持指定的距离。
Padding 属性定义控件内部的空间,该空间令控件的内容(例如,文本属性的值)与控件的边框保持指定的距离。
AutoSize 属性告诉控件根据内容自动调整自身大小。 它不会把自身尺寸调整为小于其原始的 Size 属性值,且会参考其 Padding 属性值。
在类的公开部分,编写设置和返回所有已声明的类变量值的方法:
public: //--- (1) Set and (2) return the default text color of all panel objects void ForeColor(const color clr) { this.m_fore_color=clr; } color ForeColor(void) const { return this.m_fore_color; } //--- (1) Set and (2) return the frame style void BorderStyle(const ENUM_FRAME_STYLE style) { this.m_border_style=style; } ENUM_FRAME_STYLE BorderStyle(void) const { return this.m_border_style; } //--- (1) Set and (2) return the auto scrollbar flag void AutoScroll(const bool flag) { this.m_autoscroll=flag; } bool AutoScroll(void) { return this.m_autoscroll; } //--- Set the (1) field width, (2) height, (3) the height of all fields around the control during auto scrolling void AutoScrollMarginWidth(const int value) { this.m_autoscroll_margin[0]=value; } void AutoScrollMarginHeight(const int value) { this.m_autoscroll_margin[1]=value; } void AutoScrollMarginAll(const int value) { this.AutoScrollMarginWidth(value); this.AutoScrollMarginHeight(value); } //--- Return the (1) field width and (2) height around the control during auto scrolling int AutoScrollMarginWidth(void) const { return this.m_autoscroll_margin[0]; } int AutoScrollMarginHeight(void) const { return this.m_autoscroll_margin[1]; } //--- (1) Set and (2) return the flag of the element auto resizing depending on the content void AutoSize(const bool flag) { this.m_autosize=flag; } bool AutoSize(void) { return this.m_autosize; } //--- (1) Set and (2) return the mode of the element auto resizing depending on the content void AutoSizeMode(const ENUM_CANV_ELEMENT_AUTO_SIZE_MODE mode) { this.m_autosize_mode=mode; } ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void) const { return this.m_autosize_mode; } //--- (1) Set and (2) return the mode of binding element borders to the container void DockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode){ this.m_dock_mode=mode; } ENUM_CANV_ELEMENT_DOCK_MODE DockMode(void) const { return this.m_dock_mode; } //--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides between the fields of this and another control void MarginLeft(const int value) { this.m_margin[0]=value; } void MarginTop(const int value) { this.m_margin[1]=value; } void MarginRight(const int value) { this.m_margin[2]=value; } void MarginBottom(const int value) { this.m_margin[3]=value; } void MarginAll(const int value) { this.MarginLeft(value); this.MarginTop(value); this.MarginRight(value); this.MarginBottom(value); } //--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields of this and another control int MarginLeft(void) const { return this.m_margin[0]; } int MarginTop(void) const { return this.m_margin[1]; } int MarginRight(void) const { return this.m_margin[2]; } int MarginBottom(void) const { return this.m_margin[3]; } //--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control void PaddingLeft(const int value) { this.m_padding[0]=value; } void PaddingTop(const int value) { this.m_padding[1]=value; } void PaddingRight(const int value) { this.m_padding[2]=value; } void PaddingBottom(const int value) { this.m_padding[3]=value; } void PaddingAll(const int value) { this.PaddingLeft(value); this.PaddingTop(value); this.PaddingRight(value); this.PaddingBottom(value); } //--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields inside the control int PaddingLeft(void) const { return this.m_padding[0]; } int PaddingTop(void) const { return this.m_padding[1]; } int PaddingRight(void) const { return this.m_padding[2]; } int PaddingBottom(void) const { return this.m_padding[3]; } //--- Constructors CPanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); CPanel(const int subwindow, const string name, const int x, const int y, const int w, const int h); CPanel(const string name, const int x, const int y, const int w, const int h); CPanel(const string name) : CForm(::ChartID(),0,name,0,0,0,0) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_FORE_COLOR; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //--- Destructor ~CPanel(); }; //+------------------------------------------------------------------+
对于其中一些属性,可以为对象每一侧对应的每个属性同时进行设置。
例如,对于 MS Visual Studio 中的 Margin 值,可以分别设置每个属性,亦可同时设置所有四个属性:
我们有四个类构造函数:指定(1)图表 ID、图表子窗口、对象名称和坐标尺寸;(2)当前图表子窗口、对象名称和坐标尺寸;(3)对象名称和坐标尺寸;(4)坐标和尺寸为零的对象名称:
//+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CPanel::CPanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CForm(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_FORE_COLOR; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //+------------------------------------------------------------------+ //| Current chart constructor specifying the subwindow | //+------------------------------------------------------------------+ CPanel::CPanel(const int subwindow, const string name, const int x, const int y, const int w, const int h) : CForm(::ChartID(),subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_FORE_COLOR; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //+------------------------------------------------------------------+ //| Constructor on the current chart in the main chart window | //+------------------------------------------------------------------+ CPanel::CPanel(const string name, const int x, const int y, const int w, const int h) : CForm(::ChartID(),0,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_FORE_COLOR; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //+------------------------------------------------------------------+
在每个构造函数的初始化代码中,将必要的参数传递给父类构造函数。
接下来,在构造函数主体中,设置图形元素类型、函数库对象类型、默认的面板文本颜色,为所有边设置边距为 3,填充设置为 0,并初始化父类变量。
对于在终端图表上简单地创建面板对象,这就就足够了。 面板对象的所有其它内容将在后续的文章中实现。
在类析构函数中,调用父类的逆初始化方法:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CPanel::~CPanel() { CForm::Deinitialize(); } //+------------------------------------------------------------------+
现在,我们需要改进 \MQL5\Include\DoEasy\Collections\b0>GraphElementsCollection.mqh 当中的图形元素集合类。
取代窗体对象文件,包含面板对象文件:
//+------------------------------------------------------------------+ //| GraphElementsCollection.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Services\Select.mqh" #include "..\Objects\Graph\WForms\Containers\Panel.mqh" #include "..\Objects\Graph\Standard\GStdVLineObj.mqh"
由于面板对象是从窗体对象派生而来的,因此其父层次结构的所有对象都将在集合类中可见。
在类的公开部分,编写两个方法,按图表和对象 ID 返回图形元素列表,以及按图表 ID 和对象名称返回图形元素列表:
//--- Return the list of graphical objects by chart ID and group CArrayObj *GetListStdGraphObjByGroup(const long chart_id,const int group) { CArrayObj *list=GetList(GRAPH_OBJ_PROP_CHART_ID,0,chart_id,EQUAL); return CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_GROUP,0,group,EQUAL); } //--- Return the list of graphical elements by chart and object IDs CArrayObj *GetListCanvElementByID(const long chart_id,const int element_id) { CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL); return CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL);; } //--- Return the list of graphical elements by chart ID and object name CArrayObj *GetListCanvElementByName(const long chart_id,const string name) { CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL); return CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,name,EQUAL);; } //--- Constructor
我早前曾反复思考过这种方法的逻辑。 在此,我们简单地按必要的参数对列表进行排序,并返回结果列表,该列表的特征是指向从集合列表中找到的对象指针。
如果未找到对象,这些方法将返回 NULL。
在类主体的末尾,编写创建图形元素、窗体和面板对象的方法:
//--- Create a graphical element object on canvas on a specified chart and subwindow int CreateElement(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, const color clr, const uchar opacity, const bool movable, const bool activity, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,activity,redraw); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.Erase(clr,opacity,redraw); return obj.ID(); } //--- Create a graphical element object on canvas on a specified chart and subwindow with the vertical gradient filling int CreateElementVGradient(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr[0],opacity,movable,activity,redraw); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.Erase(clr,opacity,true,false,redraw); return obj.ID(); } //--- Create a graphical element object on canvas on a specified chart and subwindow with the horizontal gradient filling int CreateElementHGradient(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr[0],opacity,movable,activity,redraw); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.Erase(clr,opacity,false,false,redraw); return obj.ID(); } //--- Create a graphical element object on canvas on a specified chart and subwindow with the cyclic vertical gradient filling int CreateElementVGradientCicle(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr[0],opacity,movable,activity,redraw); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.Erase(clr,opacity,true,true,redraw); return obj.ID(); } //--- Create a graphical element object on canvas on a specified chart and subwindow with the cyclic horizontal gradient filling int CreateElementHGradientCicle(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr[0],opacity,movable,activity,redraw); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.Erase(clr,opacity,false,true,redraw); return obj.ID(); } //--- Create a graphical object form object on canvas on a specified chart and subwindow int CreateForm(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, const color clr, const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr); obj.SetColorFrame(clr); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,redraw); return obj.ID(); } //--- Create a graphical object form object on canvas on a specified chart and subwindow with the vertical gradient filling int CreateFormVGradient(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr[0]); obj.SetColorFrame(clr[0]); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,true,false,redraw); return obj.ID(); } //--- Create a graphical object form object on canvas on a specified chart and subwindow with the horizontal gradient filling int CreateFormHGradient(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr[0]); obj.SetColorFrame(clr[0]); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,false,false,redraw); return obj.ID(); } //--- Create a graphical object form object on canvas on a specified chart and subwindow with the cyclic vertical gradient filling int CreateFormVGradientCicle(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr[0]); obj.SetColorFrame(clr[0]); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,true,true,redraw); return obj.ID(); } //--- Create a graphical object form object on canvas on a specified chart and subwindow with the cyclic horizontal gradient filling int CreateFormHGradientCicle(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr[0]); obj.SetColorFrame(clr[0]); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,false,true,redraw); return obj.ID(); } //--- Create graphical object WinForms Panel object on canvas on a specified chart and subwindow int CreatePanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, const color clr, const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CPanel *obj=new CPanel(chart_id,subwindow,name,x,y,w,h); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr); obj.SetColorFrame(clr); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,redraw); return obj.ID(); } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+
创建元素和形状的方法则几乎相同。 唯一的区别在于采用颜色填充背景的方法。 它要么是单一的永久性颜色,要么是采用渐变色填充。 渐变色填充有几种类型:垂直、水平、和垂直和水平循环。 创建一个对象后,会立即把它添加到图形元素的集合列表当中,并为其设置所需的最小一套属性(调用时会传递给方法)。
在重置除指定窗体以外的所有窗体的交互标志的方法中,将对象类型更改为元素,因为图形元素是构建 GUI 元素的最小对象:
//+--------------------------------------------------------------------+ //| Reset all interaction flags for all forms except the specified one | //+--------------------------------------------------------------------+ void CGraphElementsCollection::ResetAllInteractionExeptOne(CGCnvElement *form_exept) { //--- In the loop by all graphical element collection class objects int total=this.m_list_all_canv_elm_obj.Total(); for(int i=0;i<total;i++) { //--- get the pointer to the object CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(i); //--- if failed to receive the pointer, or it is not a form, or it is not a form whose pointer has been passed to the method, move on if(obj==NULL || obj.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_FORM || (obj.Name()==form_exept.Name() && obj.ChartID()==form_exept.ChartID())) continue; //--- Reset the interaction flag for the current form in the loop obj.SetInteraction(false); } } //+------------------------------------------------------------------+
由于我们现在已拥有创建图形元素、窗体和面板的方法,故此不再需要 \MQL5\Include\DoEasy\Engine.mqh 里将图形元素添加到集合列表之中的方法。 那么,我们就把它删除:
//--- Return the list of graphical elements on canvas CArrayObj *GetListCanvElement(void) { return this.m_graph_objects.GetListCanvElm(); } //--- Add the graphical element on canvas to the collection bool GraphAddCanvElmToCollection(CGCnvElement *element) { return this.m_graph_objects.AddCanvElmToCollection(element); } //--- Fill in the array with IDs of the charts opened in the terminal void GraphGetArrayChartsID(long &array_charts_id[])
把它替换为按照图表和对象 ID 返回图形元素列表的方法,以及按照图表 ID 和对象名称返回图形元素列表的方法:
//--- Return the list of graphical elements on canvas CArrayObj *GetListCanvElement(void) { return this.m_graph_objects.GetListCanvElm(); } //--- Return the list of graphical elements by chart and object IDs CArrayObj *GetListCanvElementByID(const long chart_id,const int element_id) { return this.m_graph_objects.GetListCanvElementByID(chart_id,element_id); } //--- Return the list of graphical elements by chart ID and object name CArrayObj *GetListCanvElementByName(const long chart_id,const string name) { return this.m_graph_objects.GetListCanvElementByName(chart_id,name); } //--- Fill in the array with IDs of the charts opened in the terminal void GraphGetArrayChartsID(long &array_charts_id[])
这两个方法只是简单地返回来自我们上面研究的图形元素集合类的同名方法的请求结果。
现在进行测试的所有准备均已就绪。
测试
为了执行测试,我们延用来自前一篇文章中的 EA,并将其保存到 \MQL5\Experts\TestDoEasy\Part101\,命名为 TestDoEasyPart101.mq5。
我将保留前一篇文章中创建的三个窗体对象,并添加三个元素对象,和一个面板对象。
在每一个对象上,显示其在图形元素集合中的名称和 ID 的本文。
在 OnInit() 处理程序里,调用本文中的方法,把所有创建的对象加到集合类之中::
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set EA global variables ArrayResize(array_clr,2); // Array of gradient filling colors array_clr[0]=C'26,100,128'; // Original ≈Dark-azure color array_clr[1]=C'35,133,169'; // Lightened original color //--- Create the array with the current symbol and set it to be used in the library string array[1]={Symbol()}; engine.SetUsedSymbols(array); //--- Create the timeseries object for the current symbol and period, and show its description in the journal engine.SeriesCreate(Symbol(),Period()); engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions //--- Create form objects int obj_id=WRONG_VALUE; CArrayObj *list=NULL; CForm *form=NULL; for(int i=0;i<FORMS_TOTAL;i++) { obj_id=engine.GetGraphicObjCollection().CreateFormVGradient(ChartID(),0,"Form_0"+string(i+1),30,(form==NULL ? 100 : form.BottomEdge()+20),100,30,array_clr,245,true,true); list=engine.GetListCanvElementByID(ChartID(),obj_id); form=list.At(0); if(form==NULL) continue; //--- Set ZOrder to zero, display the text describing the gradient type and update the form //--- Text parameters: the text coordinates and the anchor point in the form center //--- Create a new text animation frame with the ID of 0 and display the text on the form form.SetZorder(0,false); form.TextOnBG(0,"Form: ID "+(string)form.ID()+", ZOrder "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,false); } //--- Create four graphical elements CGCnvElement *elm=NULL; array_clr[0]=C'0x65,0xA4,0xA9'; array_clr[1]=C'0x48,0x75,0xA2'; //--- Vertical gradient obj_id=engine.GetGraphicObjCollection().CreateElementVGradient(NULL,0,"CElmVG",form.RightEdge()+50,20,200,50,array_clr,127,true,true,true); list=engine.GetGraphicObjCollection().GetListCanvElementByID(ChartID(),obj_id); elm=list.At(0); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,"Element: ID "+(string)elm.ID(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Vertical cyclic gradient obj_id=engine.GetGraphicObjCollection().CreateElementVGradientCicle(NULL,0,"CElmVGC",form.RightEdge()+50,80, 200,50,array_clr,127,true,true,true); list=engine.GetGraphicObjCollection().GetListCanvElementByID(ChartID(),obj_id); elm=list.At(0); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,"Element: ID "+(string)elm.ID(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Horizontal gradient obj_id=engine.GetGraphicObjCollection().CreateElementHGradient(NULL,0,"CElmHG",form.RightEdge()+50,140,200,50,array_clr,127,true,true,true); list=engine.GetGraphicObjCollection().GetListCanvElementByID(ChartID(),obj_id); elm=list.At(0); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,"Element: ID "+(string)elm.ID(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Horizontal cyclic gradient obj_id=engine.GetGraphicObjCollection().CreateElementHGradientCicle(NULL,0,"CElmHGC",form.RightEdge()+50,200,200,50,array_clr,127,true,true,false); list=engine.GetGraphicObjCollection().GetListCanvElementByID(ChartID(),obj_id); elm=list.At(0); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,"Element: ID "+(string)elm.ID(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Create WinForms Panel object CPanel *pnl=NULL; obj_id=engine.GetGraphicObjCollection().CreatePanel(ChartID(),0,"WFPanel",elm.RightEdge()+50,50,150,150,array_clr[0],200,true,true,false,true); list=engine.GetListCanvElementByID(ChartID(),obj_id); pnl=list.At(0); if(pnl!=NULL) { pnl.SetFontSize(10); pnl.TextOnBG(0,"WinForm Panel: ID "+(string)pnl.ID(),4,2,FRAME_ANCHOR_LEFT_TOP,pnl.ForeColor(),pnl.Opacity()); pnl.Update(true); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
形状对象用垂直渐变填充,而每个元素对象则取用其自己类型的渐变色填充。 面板对象则用一种颜色填充。
编译 EA,并在图表上启动它:
窗体会对鼠标移动作出反应,并始终置于已添加到图表的图形对象的顶部。 元素对象的渐变填充绘制无误,并且面板对象只有一种颜色。 但是,无论是元素还是面板都不会对鼠标作出反应,且都锁定在所有图形对象之下的背景中。 这是因为我只需处理窗体对象的鼠标事件。 面板本质上是一个窗体,这个事实并不重要,因为我只处理显式的 CForm 类。 我稍后会修复这一点。
下一步是什么?
在下一篇文章中,我将继续开发 WinForms 面板对象类。
*上一系列的最后一篇文章:
DoEasy 函数库中的图形(第一百部分):改进扩展标准图形对象的处理
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/10663