
DoEasy. 控件(第 6 部分):面板控件,自动调整容器大小来适应内部内容
内容
概述
在本文中,我将实现面板自动调整大小,以防绑定到面板的对象将 Dock 属性设置为激活。 如果任何绑定对象设置了该特性,则它应位于其绑定位置,同时面板应调整自身大小,可容纳绑定到该面板的所有对象组合后的大小。 换言之,面板大小会在附着其内的任意对象 “Dock” 属性设置发生变化后自动调整。 如果有大量附着对象,它们一个接一个地设置该属性,则面板应调整其大小,以使其更改的内部内容与每个下一个绑定对象相匹配。
若每个新对象排列变化都调整一次面板大小,会导致不愉快的视觉效果。 为避免这样的问题,我做了优化,把绑定到面板的对象修改统合到批处理。 只有在最后一个绑定对象放置到其正确位置后,面板才调整其可视大小。
在本文中,我将继续优化容器中对象安置的批量处理流程。
除了处理 WinForms 对象以外,我还为先前 MetaTrader 5 Build 3260 发布的品种函数库对象添加了新属性:
- SYMBOL_SWAP_SUNDAY
- SYMBOL_SWAP_MONDAY
- SYMBOL_SWAP_TUESDAY
- SYMBOL_SWAP_WEDNESDAY
- SYMBOL_SWAP_THURSDAY
- SYMBOL_SWAP_FRIDAY
- SYMBOL_SWAP_SATURDAY
使用这些值可以获得一周中特定日期的掉期计算利率。 1 — 单重掉期利率,3 — 三重掉期利率,0 — 无掉期利率。
新属性将在新创建的 WinForms 对象的类变量里添加。 同时,所有函数库对象都是根据专门创建函数库的第一篇文章中讲述的特定概念构建的:每个对象都有一组属性,分别定义在整数型、实数型和字符串型三个对象属性枚举当中。 在本文中,我也要把以前添加的所有新 WinForms 对象的属性变为这些枚举的常量。 在这种情况下,新属性将位于每个图形对象的常规特性列表当中。 稍后,这将允许在图形元素中显示所有 WinForms 对象属性,例如,在终端中可视化构建 EA 或 MS Visual Studio 等指标图形外壳的 GUI 程序。
改进库类
在 \MQL5\Include\DoEasy\Data.mqh 里,添加新的消息索引:
MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3245, // Property not supported in MetaTrader 5 versions lower than 3245 MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3260, // Property not supported in MetaTrader 5 versions lower than 3260 MSG_LIB_PROP_NOT_SUPPORTED_POSITION, // Property not supported for position
...
MSG_SYM_PROP_SUBSCRIPTION_DELAY, // Delay for quotes passed by symbol for instruments working on subscription basis MSG_SYM_PROP_SWAP_SUNDAY, // Swap accrual ratio. Sunday MSG_SYM_PROP_SWAP_MONDAY, // Swap accrual ratio. Monday MSG_SYM_PROP_SWAP_TUESDAY, // Swap accrual ratio. Tuesday MSG_SYM_PROP_SWAP_WEDNESDAY, // Swap accrual ratio. Wednesday MSG_SYM_PROP_SWAP_THURSDAY, // Swap accrual ratio. Thursday MSG_SYM_PROP_SWAP_FRIDAY, // Swap accrual ratio. Friday MSG_SYM_PROP_SWAP_SATURDAY, // Swap accrual ratio. Saturday MSG_SYM_PROP_SWAP_1, // Single swap accrual MSG_SYM_PROP_SWAP_3, // Triple swap accrual MSG_SYM_PROP_SWAP_0, // No swap accrual //--- MSG_SYM_PROP_BIDHIGH, // Maximal Bid of the day
及与新增索引相对应的消息:
{"Свойство не поддерживается в MetaTrader5 версии ниже 3245","The property is not supported in MetaTrader5, build lower than 3245"}, {"Свойство не поддерживается в MetaTrader5 версии ниже 3260","The property is not supported in MetaTrader5, build lower than 3260"}, {"Свойство не поддерживается у позиции","Property not supported for position"},
...
{"Размер задержки у котировок, передаваемых по символу, для инструментов, работающих по подписке","Delay size for quotes transmitted per symbol for instruments working by subscription"}, {"Коэффициент начисления свопов. Воскресение","Swap rate. Sunday"}, {"Коэффициент начисления свопов. Понедельник","Swap rate. Monday"}, {"Коэффициент начисления свопов. Вторник","Swap rate. Tuesday"}, {"Коэффициент начисления свопов. Среда","Swap rate. Wednesday"}, {"Коэффициент начисления свопов. Четверг","Swap rate. Thursday"}, {"Коэффициент начисления свопов. Пятница","Swap rate. Friday"}, {"Коэффициент начисления свопов. Суббота","Swap rate. Saturday"}, {"Одиночное начисление свопов","Single swap accrual"}, {"Тройное начисление свопов","Triple swap accrual"}, {"Начисление свопов отсутствует","No accrual"}, {"Максимальный Bid за день","Maximal Bid of the day"},
在 \MQL5\Include\DoEasy\Defines.mqh,即在品种对象的实数型属性的枚举中,加入新属性,并把实数型属性的个数从 68 增加到 75:
SYMBOL_PROP_PRICE_SENSITIVITY, // Option/warrant sensitivity. Shows by how many points the price of the option's underlying asset should change so that the price of the option changes by one point SYMBOL_PROP_SWAP_SUNDAY, // Swap accrual ratio (Sunday) SYMBOL_PROP_SWAP_MONDAY, // Swap accrual ratio (Monday) SYMBOL_PROP_SWAP_TUESDAY, // Swap accrual ratio (Tuesday) SYMBOL_PROP_SWAP_WEDNESDAY, // Swap accrual ratio (Wednesday) SYMBOL_PROP_SWAP_THURSDAY, // Swap accrual ratio (Thursday) SYMBOL_PROP_SWAP_FRIDAY, // Swap accrual ratio (Friday) SYMBOL_PROP_SWAP_SATURDAY, // Swap accrual ratio (Saturday) }; #define SYMBOL_PROP_DOUBLE_TOTAL (75) // Total number of real properties #define SYMBOL_PROP_DOUBLE_SKIP (0) // Number of real symbol properties not used in sorting //+------------------------------------------------------------------+
为了能够对品种对象进行排序,并根据新属性进行选择,添加对应于新属性的常数到品种对象可能的排序标准枚举当中:
SORT_BY_SYMBOL_PRICE_SENSITIVITY, // Sort by option/warrant sensitivity SORT_BY_SYMBOL_SWAP_SUNDAY, // Sort by swap accrual ratio (Sunday) SORT_BY_SYMBOL_SWAP_MONDAY, // Sort by swap accrual ratio (Monday) SORT_BY_SYMBOL_SWAP_TUESDAY, // Sort by swap accrual ratio (Tuesday) SORT_BY_SYMBOL_SWAP_WEDNESDAY, // Sort by swap accrual ratio (Wednesday) SORT_BY_SYMBOL_SWAP_THURSDAY, // Sort by swap accrual ratio (Thursday) SORT_BY_SYMBOL_SWAP_FRIDAY, // Sort by swap accrual ratio (Friday) SORT_BY_SYMBOL_SWAP_SATURDAY, // Sort by swap accrual ratio (Saturday) //--- Sort by string properties SORT_BY_SYMBOL_NAME = FIRST_SYM_STR_PROP, // Sort by a symbol name
在基于画布的图形元素整数型属性的枚举中,在 WinForms 对象里添加对应的新属性,并把对象整数型属性的数量从 25 增加到 38:
CANV_ELEMENT_PROP_ENABLED, // Element availability flag CANV_ELEMENT_PROP_FORE_COLOR, // Default text color for all control objects CANV_ELEMENT_PROP_BOLD_TYPE, // Font width type CANV_ELEMENT_PROP_BORDER_STYLE, // Control frame style CANV_ELEMENT_PROP_AUTOSIZE, // Flag of the element auto resizing depending on the content CANV_ELEMENT_PROP_DOCK_MODE, // Mode of binding control borders to the container CANV_ELEMENT_PROP_MARGIN_TOP, // Top margin between the fields of this and another control CANV_ELEMENT_PROP_MARGIN_BOTTOM, // Bottom margin between the fields of this and another control CANV_ELEMENT_PROP_MARGIN_LEFT, // Left margin between the fields of this and another control CANV_ELEMENT_PROP_MARGIN_RIGHT, // Right margin between the fields of this and another control CANV_ELEMENT_PROP_PADDING_TOP, // Top margin inside the control CANV_ELEMENT_PROP_PADDING_BOTTOM, // Bottom margin inside the control CANV_ELEMENT_PROP_PADDING_LEFT, // Left margin inside the control CANV_ELEMENT_PROP_PADDING_RIGHT, // Right margin inside the control }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (38) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting
在可能的 WinForms 对象排序准则的枚举中,添加依据新加整数型属性排序:
SORT_BY_CANV_ELEMENT_ENABLED, // Sort by the element availability flag SORT_BY_CANV_ELEMENT_FORE_COLOR, // Sort by default text color for all control objects SORT_BY_CANV_ELEMENT_BOLD_TYPE, // Sort by font width type SORT_BY_CANV_ELEMENT_BORDER_STYLE, // Sort by control frame style SORT_BY_CANV_ELEMENT_AUTOSIZE, // Sort by the flag of the element auto resizing depending on the content SORT_BY_CANV_ELEMENT_DOCK_MODE, // Sort by mode of binding control borders to the container SORT_BY_CANV_ELEMENT_MARGIN_TOP, // Sort by top margin between the fields of this and another control SORT_BY_CANV_ELEMENT_MARGIN_BOTTOM, // Sort by bottom margin between the fields of this and another control SORT_BY_CANV_ELEMENT_MARGIN_LEFT, // Sort by left margin between the fields of this and another control SORT_BY_CANV_ELEMENT_MARGIN_RIGHT, // Sort by right margin between the fields of this and another control SORT_BY_CANV_ELEMENT_PADDING_TOP, // Sort by top margin inside the control SORT_BY_CANV_ELEMENT_PADDING_BOTTOM, // Sort by bottom margin inside the control SORT_BY_CANV_ELEMENT_PADDING_LEFT, // Sort by left margin inside the control SORT_BY_CANV_ELEMENT_PADDING_RIGHT, // Sort by right margin inside the control //--- Sort by real properties //--- Sort by string properties SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Sort by an element object name SORT_BY_CANV_ELEMENT_NAME_RES, // Sort by the graphical resource name }; //+------------------------------------------------------------------+
现在,WinForms 对象只能依据其独有的图形对象属性进行选择和排序。
由于新属性已添加到品种对象,我们需要在品种对象类中实现针对它们的处理。
在 \MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh 类文件里进行必要的改进。
在类的受保护部分,即从参数中接收和返回选定品种的实数型属性的方法模块中,声明返回周内指定日的掉期利率的方法:
double SymbolMarginHedged(void) const; double SymbolSwapRatio(ENUM_DAY_OF_WEEK day)const; bool SymbolMarginLong(void);
在类的公开部分,声明返回周内指定日的掉期利率的方法:
string GetSectorDescription(void) const; string GetIndustryDescription(void) const; string GetSwapRatioDescription(const ENUM_DAY_OF_WEEK day)const;
在简化的访问品种对象实数型属性的方法模块中,编写返回周内每天的掉期利率的方法,并声明返回周内指定日的掉期利率的方法:
double PriceSensitivity(void) const { return this.GetProperty(SYMBOL_PROP_PRICE_SENSITIVITY); } double SwapRatioSunday(void) const { return this.GetProperty(SYMBOL_PROP_SWAP_SUNDAY); } double SwapRatioMonday(void) const { return this.GetProperty(SYMBOL_PROP_SWAP_MONDAY); } double SwapRatioTuesday(void) const { return this.GetProperty(SYMBOL_PROP_SWAP_TUESDAY); } double SwapRatioWednesday(void) const { return this.GetProperty(SYMBOL_PROP_SWAP_WEDNESDAY); } double SwapRatioThursday(void) const { return this.GetProperty(SYMBOL_PROP_SWAP_THURSDAY); } double SwapRatioFriday(void) const { return this.GetProperty(SYMBOL_PROP_SWAP_FRIDAY); } double SwapRatioSaturday(void) const { return this.GetProperty(SYMBOL_PROP_SWAP_SATURDAY); } double SwapRatioDay(const ENUM_DAY_OF_WEEK day) const; double NormalizedPrice(const double price) const;
在接收和设置跟踪属性参数变化的方法模块中,添加处理新品种对象属性的方法:
//--- Option/warrant sensitivity //--- setting the controlled maximum Bid price (1) increase, (2) decrease value and (3) option/warrant sensitivity control level in points //--- getting (4) option/warrant sensitivity change value in points, //--- getting the flag of the option/warrant sensitivity change exceeding the (5) increase, (6) decrease value void SetControlPriceSensitivityInc(const double value) { this.SetControlledValueINC(SYMBOL_PROP_PRICE_SENSITIVITY,::fabs(value)); } void SetControlPriceSensitivityDec(const double value) { this.SetControlledValueDEC(SYMBOL_PROP_PRICE_SENSITIVITY,::fabs(value)); } void SetControlPriceSensitivityLevel(const double value) { this.SetControlledValueLEVEL(SYMBOL_PROP_PRICE_SENSITIVITY,::fabs(value)); } double GetValueChangedPriceSensitivity(void) const { return this.GetPropDoubleChangedValue(SYMBOL_PROP_PRICE_SENSITIVITY); } bool IsIncreasedPriceSensitivity(void) const { return (bool)this.GetPropDoubleFlagINC(SYMBOL_PROP_PRICE_SENSITIVITY); } bool IsDecreasedPriceSensitivity(void) const { return (bool)this.GetPropDoubleFlagDEC(SYMBOL_PROP_PRICE_SENSITIVITY); } //--- Swap accrual ratio //--- setting the controlled Sunday swap accrual ratio (1) increase, (2) decrease and (3) control levels //--- get (4) the swap accrual ratio change (Sunday), //--- get the swap accrual ratio change for Sunday exceeding the (5) increase and (6) decrease values void SetControlSwapSundayInc(const double value) { this.SetControlledValueINC(SYMBOL_PROP_SWAP_SUNDAY,::fabs(value)); } void SetControlSwapSundayDec(const double value) { this.SetControlledValueDEC(SYMBOL_PROP_SWAP_SUNDAY,::fabs(value)); } void SetControlSwapSundayLevel(const double value) { this.SetControlledValueLEVEL(SYMBOL_PROP_SWAP_SUNDAY,::fabs(value)); } double GetValueChangedSwapSunday(void) const { return this.GetPropDoubleChangedValue(SYMBOL_PROP_SWAP_SUNDAY); } bool IsIncreasedSwapSunday(void) const { return (bool)this.GetPropDoubleFlagINC(SYMBOL_PROP_SWAP_SUNDAY); } bool IsDecreasedSwapSunday(void) const { return (bool)this.GetPropDoubleFlagDEC(SYMBOL_PROP_SWAP_SUNDAY); } //--- setting the controlled Monday swap accrual ratio (1) increase, (2) decrease and (3) control levels //--- get (4) the swap accrual ratio change (Monday), //--- get the swap accrual ratio change for Monday exceeding the (5) increase and (6) decrease values void SetControlSwapMondayInc(const double value) { this.SetControlledValueINC(SYMBOL_PROP_SWAP_MONDAY,::fabs(value)); } void SetControlSwapMondayDec(const double value) { this.SetControlledValueDEC(SYMBOL_PROP_SWAP_MONDAY,::fabs(value)); } void SetControlSwapMondayLevel(const double value) { this.SetControlledValueLEVEL(SYMBOL_PROP_SWAP_MONDAY,::fabs(value)); } double GetValueChangedSwapMonday(void) const { return this.GetPropDoubleChangedValue(SYMBOL_PROP_SWAP_MONDAY); } bool IsIncreasedSwapMonday(void) const { return (bool)this.GetPropDoubleFlagINC(SYMBOL_PROP_SWAP_MONDAY); } bool IsDecreasedSwapMonday(void) const { return (bool)this.GetPropDoubleFlagDEC(SYMBOL_PROP_SWAP_MONDAY); } //--- setting the controlled Tuesday swap accrual ratio (1) increase, (2) decrease and (3) control levels //--- get (4) the swap accrual ratio change (Tuesday), //--- get the swap accrual ratio change for Tuesday exceeding the (5) increase and (6) decrease values void SetControlSwapTuesdayInc(const double value) { this.SetControlledValueINC(SYMBOL_PROP_SWAP_TUESDAY,::fabs(value)); } void SetControlSwapTuesdayDec(const double value) { this.SetControlledValueDEC(SYMBOL_PROP_SWAP_TUESDAY,::fabs(value)); } void SetControlSwapTuesdayLevel(const double value) { this.SetControlledValueLEVEL(SYMBOL_PROP_SWAP_TUESDAY,::fabs(value)); } double GetValueChangedSwapTuesday(void) const { return this.GetPropDoubleChangedValue(SYMBOL_PROP_SWAP_TUESDAY); } bool IsIncreasedSwapTuesday(void) const { return (bool)this.GetPropDoubleFlagINC(SYMBOL_PROP_SWAP_TUESDAY); } bool IsDecreasedSwapTuesday(void) const { return (bool)this.GetPropDoubleFlagDEC(SYMBOL_PROP_SWAP_TUESDAY); } //--- setting the controlled Wednesday swap accrual ratio (1) increase, (2) decrease and (3) control levels //--- get (4) the swap accrual ratio change (Wednesday), //--- get the swap accrual ratio change for Wednesday exceeding the (5) increase and (6) decrease values void SetControlSwapWednesdayInc(const double value) { this.SetControlledValueINC(SYMBOL_PROP_SWAP_WEDNESDAY,::fabs(value)); } void SetControlSwapWednesdayDec(const double value) { this.SetControlledValueDEC(SYMBOL_PROP_SWAP_WEDNESDAY,::fabs(value)); } void SetControlSwapWednesdayLevel(const double value) { this.SetControlledValueLEVEL(SYMBOL_PROP_SWAP_WEDNESDAY,::fabs(value)); } double GetValueChangedSwapWednesday(void) const { return this.GetPropDoubleChangedValue(SYMBOL_PROP_SWAP_WEDNESDAY); } bool IsIncreasedSwapWednesday(void) const { return (bool)this.GetPropDoubleFlagINC(SYMBOL_PROP_SWAP_WEDNESDAY); } bool IsDecreasedSwapWednesday(void) const { return (bool)this.GetPropDoubleFlagDEC(SYMBOL_PROP_SWAP_WEDNESDAY); } //--- setting the controlled Thursday swap accrual ratio (1) increase, (2) decrease and (3) control levels //--- get (4) the swap accrual ratio change (Thursday), //--- get the swap accrual ratio change for Thursday exceeding the (5) increase and (6) decrease values void SetControlSwapThursdayInc(const double value) { this.SetControlledValueINC(SYMBOL_PROP_SWAP_THURSDAY,::fabs(value)); } void SetControlSwapThursdayDec(const double value) { this.SetControlledValueDEC(SYMBOL_PROP_SWAP_THURSDAY,::fabs(value)); } void SetControlSwapThursdayLevel(const double value) { this.SetControlledValueLEVEL(SYMBOL_PROP_SWAP_THURSDAY,::fabs(value)); } double GetValueChangedSwapThursday(void) const { return this.GetPropDoubleChangedValue(SYMBOL_PROP_SWAP_THURSDAY); } bool IsIncreasedSwapThursday(void) const { return (bool)this.GetPropDoubleFlagINC(SYMBOL_PROP_SWAP_THURSDAY); } bool IsDecreasedSwapThursday(void) const { return (bool)this.GetPropDoubleFlagDEC(SYMBOL_PROP_SWAP_THURSDAY); } //--- setting the controlled Friday swap accrual ratio (1) increase, (2) decrease and (3) control levels //--- get (4) the swap accrual ratio change (Friday), //--- get the swap accrual ratio change for Friday exceeding the (5) increase and (6) decrease values void SetControlSwapFridayInc(const double value) { this.SetControlledValueINC(SYMBOL_PROP_SWAP_FRIDAY,::fabs(value)); } void SetControlSwapFridayDec(const double value) { this.SetControlledValueDEC(SYMBOL_PROP_SWAP_FRIDAY,::fabs(value)); } void SetControlSwapFridayLevel(const double value) { this.SetControlledValueLEVEL(SYMBOL_PROP_SWAP_FRIDAY,::fabs(value)); } double GetValueChangedSwapFriday(void) const { return this.GetPropDoubleChangedValue(SYMBOL_PROP_SWAP_FRIDAY); } bool IsIncreasedSwapFriday(void) const { return (bool)this.GetPropDoubleFlagINC(SYMBOL_PROP_SWAP_FRIDAY); } bool IsDecreasedSwapFriday(void) const { return (bool)this.GetPropDoubleFlagDEC(SYMBOL_PROP_SWAP_FRIDAY); } //--- setting the controlled Saturday swap accrual ratio (1) increase, (2) decrease and (3) control levels //--- get (4) the swap accrual ratio change (Saturday), //--- get the swap accrual ratio change for Saturday exceeding the (5) increase and (6) decrease values void SetControlSwapSaturdayInc(const double value) { this.SetControlledValueINC(SYMBOL_PROP_SWAP_SATURDAY,::fabs(value)); } void SetControlSwapSaturdayDec(const double value) { this.SetControlledValueDEC(SYMBOL_PROP_SWAP_SATURDAY,::fabs(value)); } void SetControlSwapSaturdayLevel(const double value) { this.SetControlledValueLEVEL(SYMBOL_PROP_SWAP_SATURDAY,::fabs(value)); } double GetValueChangedSwapSaturday(void) const { return this.GetPropDoubleChangedValue(SYMBOL_PROP_SWAP_SATURDAY); } bool IsIncreasedSwapSaturday(void) const { return (bool)this.GetPropDoubleFlagINC(SYMBOL_PROP_SWAP_SATURDAY); } bool IsDecreasedSwapSaturday(void) const { return (bool)this.GetPropDoubleFlagDEC(SYMBOL_PROP_SWAP_SATURDAY); } //--- Return a trading object CTradeObj *GetTradeObj(void) { return &this.m_trade; } }; //+------------------------------------------------------------------+
这些方法允许直接从程序中设置跟踪值,而其受管控参数则相应变更。 当这样的变更注册时,我们应该在程序中获得有关事件的信号。 在开发函数库对象的交互性时,我曾研究过它们。
在受保护的参数型类构造函数中,添加并保存品种对象的新属性:
this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_MAINTENANCE)]= this.m_margin_rate.SellStopLimit.Maintenance; this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_SUNDAY)] = this.SymbolSwapRatio(SUNDAY); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_MONDAY)] = this.SymbolSwapRatio(MONDAY); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_TUESDAY)] = this.SymbolSwapRatio(TUESDAY); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_WEDNESDAY)] = this.SymbolSwapRatio(WEDNESDAY); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_THURSDAY)] = this.SymbolSwapRatio(THURSDAY); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_FRIDAY)] = this.SymbolSwapRatio(FRIDAY); this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_SATURDAY)] = this.SymbolSwapRatio(SATURDAY); //--- Save string properties this.m_string_prop[this.IndexProp(SYMBOL_PROP_NAME)] = this.m_name;
调用 SymbolSwapRatio() 方法(如下所述)接收的数值会设置到对象属性数组当中。
我们在类主体之外编写已声明新方法的实现。
受保护方法返回周内指定日的掉期利率:
//+------------------------------------------------------------------+ //|Return the swap accrual ratio for a specified day of the week | //+------------------------------------------------------------------+ double CSymbol::SymbolSwapRatio(ENUM_DAY_OF_WEEK day) const { #ifdef __MQL4__ return 0; #else switch(day) { case MONDAY : return ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_MONDAY); case TUESDAY : return ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_TUESDAY); case WEDNESDAY : return ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_WEDNESDAY); case THURSDAY : return ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_THURSDAY); case FRIDAY : return ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_FRIDAY); case SATURDAY : return ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_SATURDAY); //---SUNDAY default : return (int)::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_SUNDAY); } #endif } //+------------------------------------------------------------------+
MQL4 没有这样的品种 — 返回零。 若是 MQL5 的情况,取决于传递给该方法的周内所在日,返回相应的品种属性。 该方法在类构造函数中为品种属性设置其对应的实数型属性的枚举值。
返回周内指定日的掉期利率的公开方法:
//+------------------------------------------------------------------+ //|Return the swap accrual ratio for a specified day of the week | //+------------------------------------------------------------------+ double CSymbol::SwapRatioDay(const ENUM_DAY_OF_WEEK day) const { switch(day) { case MONDAY : return this.SwapRatioMonday(); case TUESDAY : return this.SwapRatioTuesday(); case WEDNESDAY : return this.SwapRatioWednesday(); case THURSDAY : return this.SwapRatioThursday(); case FRIDAY : return this.SwapRatioFriday(); case SATURDAY : return this.SwapRatioSaturday(); //---SUNDAY default : return this.SwapRatioSunday(); } } //+------------------------------------------------------------------+
在此,根据传递给该方法的周内日,调用公开方法获取属性值,该方法返回在实数型对象属性枚举中设置的特定日的属性值。
在返回品种实数型属性描述的方法中,添加返回品种对象的新属性描述:
property==SYMBOL_PROP_PRICE_SENSITIVITY ? CMessage::Text(MSG_SYM_PROP_PRICE_SENSITIVITY)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==SYMBOL_PROP_SWAP_SUNDAY ? CMessage::Text(MSG_SYM_PROP_SWAP_SUNDAY)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : #ifdef __MQL5__ (::TerminalInfoInteger(TERMINAL_BUILD)<3260 ? ": ("+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3260)+")" : ": "+this.GetSwapRatioDescription(SUNDAY)) #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif ) : property==SYMBOL_PROP_SWAP_MONDAY ? CMessage::Text(MSG_SYM_PROP_SWAP_MONDAY)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : #ifdef __MQL5__ (::TerminalInfoInteger(TERMINAL_BUILD)<3260 ? ": ("+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3260)+")" : ": "+this.GetSwapRatioDescription(MONDAY)) #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif ) : property==SYMBOL_PROP_SWAP_TUESDAY ? CMessage::Text(MSG_SYM_PROP_SWAP_TUESDAY)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : #ifdef __MQL5__ (::TerminalInfoInteger(TERMINAL_BUILD)<3260 ? ": ("+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3260)+")" : ": "+this.GetSwapRatioDescription(TUESDAY)) #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif ) : property==SYMBOL_PROP_SWAP_WEDNESDAY ? CMessage::Text(MSG_SYM_PROP_SWAP_WEDNESDAY)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : #ifdef __MQL5__ (::TerminalInfoInteger(TERMINAL_BUILD)<3260 ? ": ("+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3260)+")" : ": "+this.GetSwapRatioDescription(WEDNESDAY)) #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif ) : property==SYMBOL_PROP_SWAP_THURSDAY ? CMessage::Text(MSG_SYM_PROP_SWAP_THURSDAY)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : #ifdef __MQL5__ (::TerminalInfoInteger(TERMINAL_BUILD)<3260 ? ": ("+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3260)+")" : ": "+this.GetSwapRatioDescription(THURSDAY)) #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif ) : property==SYMBOL_PROP_SWAP_FRIDAY ? CMessage::Text(MSG_SYM_PROP_SWAP_FRIDAY)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : #ifdef __MQL5__ (::TerminalInfoInteger(TERMINAL_BUILD)<3260 ? ": ("+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3260)+")" : ": "+this.GetSwapRatioDescription(FRIDAY)) #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif ) : property==SYMBOL_PROP_SWAP_SATURDAY ? CMessage::Text(MSG_SYM_PROP_SWAP_SATURDAY)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : #ifdef __MQL5__ (::TerminalInfoInteger(TERMINAL_BUILD)<3260 ? ": ("+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3260)+")" : ": "+this.GetSwapRatioDescription(SATURDAY)) #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif ) : "" ); } //+------------------------------------------------------------------+
在此处,我们检查终端构建序号,如果低于 3260,则通知此版本不支持该属性。 否则,我们将获得 MQL5 的属性描述,而对于 MQL4,我们会看到不支持该属性的通知。
返回周内指定日掉期利率说明的方法:
//+------------------------------------------------------------------+ //| Return the swap accrual ratio description | //| for a specified day of the week | //+------------------------------------------------------------------+ string CSymbol::GetSwapRatioDescription(const ENUM_DAY_OF_WEEK day) const { double ratio=this.SwapRatioDay(day); return ( ratio==0 ? CMessage::Text(MSG_SYM_PROP_SWAP_0) : ratio==1 ? CMessage::Text(MSG_SYM_PROP_SWAP_1) : ratio==3 ? CMessage::Text(MSG_SYM_PROP_SWAP_3) : ::DoubleToString(ratio,3) ); } //+------------------------------------------------------------------+
此处我们首先依据传递给方法的周内指定日获取其属性值,然后返回属性值的文本描述(如果值为 0、1 或 3),或显示由双精度值转换的文本,小数点后截取三位。 (只有在执行足够数量的测试后,才能定义所要显示的小数位数)。
为了将对象保存到图形对象文件(以及将来的所有函数库对象),我们应该使用对象属性结构。 所有属性都保存到结构当中,而结构本身又被保存到文件之中。 以同样的方式,从文件中读取数值来恢复对象属性。
鉴于我们有图形对象的新属性,所以亦要把它们添加到结构当中。
在 \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh 中,往对象结构里添加新属性:
private: int m_shift_coord_x; // Offset of the X coordinate relative to the base object int m_shift_coord_y; // Offset of the Y coordinate relative to the base object struct SData { //--- Object integer properties int id; // Element ID int type; // Graphical element type int number; // Element index in the list long chart_id; // Chart ID int subwindow; // Chart subwindow index int coord_x; // Element X coordinate on the chart int coord_y; // Element Y coordinate on the chart int width; // Element width int height; // Element height int edge_right; // Element right border int edge_bottom; // Element bottom border int act_shift_left; // Active area offset from the left edge of the element int act_shift_top; // Active area offset from the top edge of the element int act_shift_right; // Active area offset from the right edge of the element int act_shift_bottom; // Active area offset from the bottom edge of the element bool movable; // Element moveability flag bool active; // Element activity flag bool interaction; // Flag of interaction with the outside environment int coord_act_x; // X coordinate of the element active area int coord_act_y; // Y coordinate of the element active area int coord_act_right; // Right border of the element active area 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 int belong; // Graphical element affiliation color fore_color; // Default text color for all control objects int bold_type; // Font width type int border_style; // Control frame style bool autosize; // Flag of the element auto resizing depending on the content int dock_mode; // Mode of binding control borders to the container int margin_top; // Top margin between the fields of this and another control int margin_bottom; // Bottom margin between the fields of this and another control int margin_left; // Left margin between the fields of this and another control int margin_right; // Right margin between the fields of this and another control int padding_top; // Top margin inside the control int padding_bottom; // Bottom margin inside the control int padding_left; // Left margin inside the control int padding_right; // Right margin inside the control uchar opacity; // Element opacity color color_bg; // Element background color //--- 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
在创建对象结构的方法中,在结构字段中编写新加的对象属性:
this.m_struct_obj.coord_act_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM); // Bottom border of the element active area this.m_struct_obj.belong=(int)this.GetProperty(CANV_ELEMENT_PROP_BELONG); // Graphical element affiliation this.m_struct_obj.zorder=this.GetProperty(CANV_ELEMENT_PROP_ZORDER); // Priority of a graphical object for receiving the on-chart mouse click event this.m_struct_obj.fore_color=(color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR); // Default text color for all control objects this.m_struct_obj.bold_type=(int)this.GetProperty(CANV_ELEMENT_PROP_BOLD_TYPE); // Font width type this.m_struct_obj.border_style=(int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_STYLE); // Control frame style this.m_struct_obj.autosize=this.GetProperty(CANV_ELEMENT_PROP_AUTOSIZE); // Flag of the element auto resizing depending on the content this.m_struct_obj.dock_mode=(int)this.GetProperty(CANV_ELEMENT_PROP_DOCK_MODE); // Mode of binding control borders to the container this.m_struct_obj.margin_top=(int)this.GetProperty(CANV_ELEMENT_PROP_MARGIN_TOP); // Top margin between the fields of this and another control this.m_struct_obj.margin_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_MARGIN_BOTTOM); // Bottom margin between the fields of this and another control this.m_struct_obj.margin_left=(int)this.GetProperty(CANV_ELEMENT_PROP_MARGIN_LEFT); // Left margin between the fields of this and another control this.m_struct_obj.margin_right=(int)this.GetProperty(CANV_ELEMENT_PROP_MARGIN_RIGHT); // Right margin between the fields of this and another control this.m_struct_obj.padding_top=(int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_TOP); // Top margin inside the control this.m_struct_obj.padding_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_BOTTOM); // Bottom margin inside the control this.m_struct_obj.padding_left=(int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_LEFT); // Left margin inside the control this.m_struct_obj.padding_right=(int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_RIGHT); // Right margin inside the control this.m_struct_obj.color_bg=this.m_color_bg; // Element background color this.m_struct_obj.opacity=this.m_opacity; // Element opacity
在结构外实现的创建对象的方法中,添加从结构字段读取对象属性值:
this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.m_struct_obj.coord_act_bottom); // Bottom border of the element active area this.SetProperty(CANV_ELEMENT_PROP_BELONG,this.m_struct_obj.belong); // Graphical element affiliation this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR,this.m_struct_obj.fore_color); // Default text color for all control objects this.SetProperty(CANV_ELEMENT_PROP_BOLD_TYPE,this.m_struct_obj.bold_type); // Font width type this.SetProperty(CANV_ELEMENT_PROP_BORDER_STYLE,this.m_struct_obj.border_style); // Control frame style this.SetProperty(CANV_ELEMENT_PROP_AUTOSIZE,this.m_struct_obj.autosize); // Flag of the element auto resizing depending on the content this.SetProperty(CANV_ELEMENT_PROP_DOCK_MODE,this.m_struct_obj.dock_mode); // Mode of binding control borders to the container this.SetProperty(CANV_ELEMENT_PROP_MARGIN_TOP,this.m_struct_obj.margin_top); // Top margin between the fields of this and another control this.SetProperty(CANV_ELEMENT_PROP_MARGIN_BOTTOM,this.m_struct_obj.margin_bottom); // Bottom margin between the fields of this and another control this.SetProperty(CANV_ELEMENT_PROP_MARGIN_LEFT,this.m_struct_obj.margin_left); // Left margin between the fields of this and another control this.SetProperty(CANV_ELEMENT_PROP_MARGIN_RIGHT,this.m_struct_obj.margin_right); // Right margin between the fields of this and another control this.SetProperty(CANV_ELEMENT_PROP_PADDING_TOP,this.m_struct_obj.padding_top); // Top margin inside the control this.SetProperty(CANV_ELEMENT_PROP_PADDING_BOTTOM,this.m_struct_obj.padding_bottom); // Bottom margin inside the control this.SetProperty(CANV_ELEMENT_PROP_PADDING_LEFT,this.m_struct_obj.padding_left); // Left margin inside the control this.SetProperty(CANV_ELEMENT_PROP_PADDING_RIGHT,this.m_struct_obj.padding_right); // Right margin inside the control this.SetZorder(this.m_struct_obj.zorder,false); // Priority of a graphical object for receiving the on-chart mouse click event this.m_color_bg=this.m_struct_obj.color_bg; // Element background color this.m_opacity=this.m_struct_obj.opacity; // Element opacity
现在,当我们开始在文件中保存函数库对象的属性时,所有图形对象均已正确保存到文件中,并能从中还原。
我们还要对 \MQL5\Include\DoEasy\objects\Graph\WForms\WinFormBase.mqh 中的 WinForms 对象基类稍加改进。
首先,我们需要能够选择这些对象,并根据其属性对其进行排序。 为达此目标,包含 CSelect 类文件:
//+------------------------------------------------------------------+ //| WinFormBase.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Form.mqh" #include "..\..\..\Services\Select.mqh" //+------------------------------------------------------------------+
现在,我们可以在所有继承的类中使用 CSelect 类,并依据 WinForms 对象属性对其进行选择和排序。
此外,在对象重绘方法中,在处理所有附着对象的循环中添加检查重绘标志,从而仅当重绘标志被激活时,才真正重绘对象。 如果情况并非如此,则无需重新绘制绑定对象(例如,为了在更改面板对象属性时,消除视觉闪烁)。 重绘标志重置后,绑定到容器的对象不会被真正重绘,因为它们的属性是在其它方法中发生了更改,而所有对象只在此处真正重绘。
因此,添加检查标志,并仅在设置了标志时才进行对象重绘:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CWinFormBase::Redraw(bool redraw) { //--- If the object type is less than the "Base WinForms object", exit if(this.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE) return; //--- Get the "Shadow" object CShadowObj *shadow=this.GetShadowObj(); //--- If the object has a shadow and the "Shadow" object exists, redraw it if(this.IsShadow() && shadow!=NULL) { //--- remove the previously drawn shadow, shadow.Erase(); //--- save the relative shadow coordinates, int x=shadow.CoordXRelative(); int y=shadow.CoordYRelative(); //--- redraw the shadow, if(redraw) shadow.Draw(0,0,shadow.Blur(),redraw); //--- restore relative shadow coordinates shadow.SetCoordXRelative(x); shadow.SetCoordYRelative(y); } //--- If the redraw flag is set, if(redraw) { //--- completely redraw the object and save its new initial look this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c,redraw); this.Done(); } //--- otherwise, remove the object else this.Erase(); //--- Redraw all bound objects with the redraw flag for(int i=0;i<this.ElementsTotal();i++) { CWinFormBase *element=this.GetElement(i); if(element==NULL) continue; if(redraw) element.Redraw(redraw); } //--- If the redraw flag is set and if this is the main object the rest are bound to, //--- redraw the chart to display changes immediately if(redraw && this.GetMain()==NULL) ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
现在,我们改进 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh 中的面板 WinForms 对象类。
在类的私密部分,声明计算 Dock 对象绑定坐标的方法:
//--- Set the underlay as a coordinate system zero void SetUnderlayAsBase(void); //--- Calculate Dock objects' binding coordinates void CalculateCoords(CArrayObj *list); protected:
我不会在本文中实现该方法,因此这样会令我们在类主体之外添加一个空方法:
//+------------------------------------------------------------------+ //| Calculate Dock objects' binding coordinates | //+------------------------------------------------------------------+ void CPanel::CalculateCoords(CArrayObj *list) { } //+------------------------------------------------------------------+
在创建“文本标签”(Label)WinForms 对象类后,我再来处理其实现,这样我们可以直观地看到根据 Dock 属性值绑定到对象时的排序,同时也能正确处理绑定到其容器的对象排列位移,而该容器又连接到主容器 — 面板。
从类的受保护部分,删除已声明的,若 Dock 对象边界最大值超出容器宽度和高度,则返回该 Dock 对象的方法:
protected: //--- Return the maximum value of Dock object borders going beyond the container by width and (2) height int GetExcessMaxX(void); int GetExcessMaxY(void); //--- Set (1) X, (2) Y coordinate, (3) width, (4) height and (5) all underlay parameters
此外,删除该方法在类主体外的实现。
现在不需要这些方法,因为我们现在可以使用 CSelect 函数库类来查找所需的值。
在类的公开部分,声明返回绑定的 WinForms 基类及更高层次对象列表的方法:
public: //--- Return the underlay CGCnvElement *GetUnderlay(void) { return this.m_underlay; } //--- Return the list of bound objects with WinForms type basic and higher CArrayObj *GetListWinFormsObj(void);
面板对象在创建时仍然无需边框。 如果面板需要框架,可以在创建对象后再添加框架。 因此,在类构造函数中把边框类型设置为不存在:
//--- 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 string name) : CWinFormBase(::ChartID(),0,name,0,0,0,0) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_PANEL); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.SetForeColor(CLR_DEF_FORE_COLOR); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(3); this.SetPaddingAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoScroll(false); this.SetAutoScrollMarginAll(0); this.SetAutoSize(false,false); this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false); this.Initialize(); if(this.CreateUnderlayObj()) this.SetUnderlayAsBase(); } //--- Destructor ~CPanel(); }; //+------------------------------------------------------------------+ //| 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) : CWinFormBase(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_PANEL); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.SetForeColor(CLR_DEF_FORE_COLOR); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(3); this.SetPaddingAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoScroll(false); this.SetAutoScrollMarginAll(0); this.SetAutoSize(false,false); this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false); this.Initialize(); if(this.CreateUnderlayObj()) this.SetUnderlayAsBase(); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); } //+------------------------------------------------------------------+
因此,在类构造函数:v中将框架类型设置为不存在
this.Initialize(); this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle()); if(this.CreateUnderlayObj()) this.SetUnderlayAsBase();
以前,如果容器(面板)尚且设置自动调整大小标志,该方法在排布绑定对象位置时,只按照绑定对象 Dock 的顺序处理,且不包含对象位置的处理。 在本文中,我将简单地从固定面板处理程序复制绑定模式处理程序。 不过,我会初步添加对参考底图的检查,而现在将使用新方法接收对象列表,该方法只返回 WinForms 对象列表。 如果面板启用了自动调整大小模式,首先将面板大小更改为原始大小,接下来,调用将面板大小调整为适配其内容的方法。 之后,在循环中,处理所有绑定对象的绑定模式,并根据其内部对象变更重新调整面板大小:
//+------------------------------------------------------------------+ //| Place bound objects in the order of their Dock binding | //+------------------------------------------------------------------+ bool CPanel::ArrangeObjects(const bool redraw) { //--- If the panel has no underlay, return 'false' if(this.m_underlay==NULL) return false; //--- Get the list of bound objects with WinForms type basic and higher CArrayObj *list=this.GetListWinFormsObj(); CWinFormBase *prev=NULL, *obj=NULL, *elm=NULL; //--- If auto resizing mode is enabled if(this.AutoSize()) { //--- Return the original panel size and then adjust it to the objects located inside it this.Resize(this.GetWidthInit(),this.GetHeightInit(),false); this.AutoSizeProcess(false); //--- In the loop by all bound objects, for(int i=0;i<list.Total();i++) { //--- Get the current and previous elements from the list obj=list.At(i); prev=list.At(i-1); //--- If there is no previous element, set the underlay as a previous element if(prev==NULL) this.SetUnderlayAsBase(); //--- If the object has not been received or its type is less than the base WinForms object or the current element has no underlay, move on if(obj==NULL) continue; int x=0, y=0; // Object binding coordinates //--- Depending on the current object binding mode... //--- Top if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP) { //--- If failed to change the object size (for the entire underlay width and by the initial object height), move on to the next one if(!obj.Resize(this.GetWidthUnderlay(),obj.GetHeightInit(),false)) continue; //--- Get the pointer to the object at the top whose edges are used to bind the current one CGCnvElement *coord_base=this.GetTopObj(); //--- Get the object binding coordinates x=this.GetCoordXUnderlay(); y=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.CoordY() : coord_base.BottomEdge()+1); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the top one whose edges will be used to bind the next one this.m_obj_top=obj; } //--- Bottom if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM) { //--- If failed to change the object size (for the entire underlay width and by the initial object height), move on to the next one if(!obj.Resize(this.GetWidthUnderlay(),obj.GetHeightInit(),false)) continue; //--- Get the pointer to the object at the bottom whose edges are used to bind the current one CGCnvElement *coord_base=this.GetBottomObj(); //--- Get the object binding coordinates x=this.GetCoordXUnderlay(); y=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.BottomEdge()-obj.Height() : coord_base.CoordY()-obj.Height()-1); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the bottom one whose edges will be used to bind the next one this.m_obj_bottom=obj; } //--- Left if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT) { //--- If failed to change the object size (for the initial object width and the entire underlay height), move on to the next one if(!obj.Resize(obj.GetWidthInit(),this.GetHeightUnderlay(),false)) continue; //--- Get the pointer to the object at the left whose edges are used to bind the current one CGCnvElement *coord_base=this.GetLeftObj(); //--- Get the object binding coordinates x=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.CoordX() : coord_base.RightEdge()+1); y=this.GetCoordYUnderlay(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the left one whose edges will be used to bind the next one this.m_obj_left=obj; } //--- Right if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT) { //--- If failed to change the object size (for the initial object width and the entire underlay height), move on to the next one if(!obj.Resize(obj.GetWidthInit(),this.GetHeightUnderlay(),false)) continue; //--- Get the pointer to the object at the right whose edges are used to bind the current one CGCnvElement *coord_base=this.GetRightObj(); //--- Get the object binding coordinates x=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? m_underlay.RightEdge()-obj.Width() : coord_base.CoordX()-obj.Width()-1); y=this.GetCoordYUnderlay(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the right one whose edges will be used to bind the next one this.m_obj_right=obj; } //--- Binding with filling if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL) { //--- If failed to change the object size (for the entire underlay width and height), move on to the next one if(!obj.Resize(this.GetWidthUnderlay(),this.GetHeightUnderlay(),false)) continue; //--- Set the underlay as a binding object this.SetUnderlayAsBase(); //--- Get the object binding coordinates x=this.GetLeftObj().CoordX(); y=this.GetTopObj().CoordY(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; } //--- No binding if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_NONE) { //--- Reset the object size obj.Resize(obj.GetWidthInit(),obj.GetHeightInit(),false); //--- Get the initial object location coordinates x=this.GetCoordXUnderlay()+obj.CoordXRelativeInit(); y=this.GetCoordYUnderlay()+obj.CoordYRelativeInit(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; } //--- Calculate and set the relative object coordinates obj.SetCoordXRelative(x-this.m_underlay.CoordX()); obj.SetCoordYRelative(y-this.m_underlay.CoordY()); } this.Resize(this.GetWidthInit(),this.GetHeightInit(),false); this.AutoSizeProcess(false); } //--- If auto resizing mode disabled else { //--- In the loop by all bound objects, for(int i=0;i<list.Total();i++) { //--- Get the current and previous elements from the list obj=list.At(i); prev=list.At(i-1); //--- If there is no previous element, set the underlay as a previous element if(prev==NULL) this.SetUnderlayAsBase(); //--- If the object has not been received or its type is less than the base WinForms object or the current element has no underlay, move on if(obj==NULL) continue; int x=0, y=0; // Object binding coordinates //--- Depending on the current object binding mode... //--- Top if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP) { //--- If failed to change the object size (for the entire underlay width and by the initial object height), move on to the next one if(!obj.Resize(this.GetWidthUnderlay(),obj.GetHeightInit(),false)) continue; //--- Get the pointer to the object at the top whose edges are used to bind the current one CGCnvElement *coord_base=this.GetTopObj(); //--- Get the object binding coordinates x=this.GetCoordXUnderlay(); y=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.CoordY() : coord_base.BottomEdge()+1); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the top one whose edges will be used to bind the next one this.m_obj_top=obj; } //--- Bottom if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM) { //--- If failed to change the object size (for the entire underlay width and by the initial object height), move on to the next one if(!obj.Resize(this.GetWidthUnderlay(),obj.GetHeightInit(),false)) continue; //--- Get the pointer to the object at the bottom whose edges are used to bind the current one CGCnvElement *coord_base=this.GetBottomObj(); //--- Get the object binding coordinates x=this.GetCoordXUnderlay(); y=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.BottomEdge()-obj.Height()-1 : coord_base.CoordY()-obj.Height()-1); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the bottom one whose edges will be used to bind the next one this.m_obj_bottom=obj; } //--- Left if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT) { //--- If failed to change the object size (for the initial object width and the entire underlay height), move on to the next one if(!obj.Resize(obj.GetWidthInit(),this.GetHeightUnderlay(),false)) continue; //--- Get the pointer to the object at the left whose edges are used to bind the current one CGCnvElement *coord_base=this.GetLeftObj(); //--- Get the object binding coordinates x=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.CoordX() : coord_base.RightEdge()+1); y=this.GetCoordYUnderlay(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the left one whose edges will be used to bind the next one this.m_obj_left=obj; } //--- Right if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT) { //--- If failed to change the object size (for the initial object width and the entire underlay height), move on to the next one if(!obj.Resize(obj.GetWidthInit(),this.GetHeightUnderlay(),false)) continue; //--- Get the pointer to the object at the right whose edges are used to bind the current one CGCnvElement *coord_base=this.GetRightObj(); //--- Get the object binding coordinates x=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? m_underlay.RightEdge()-obj.Width() : coord_base.CoordX()-obj.Width()-1); y=this.GetCoordYUnderlay(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the right one whose edges will be used to bind the next one this.m_obj_right=obj; } //--- Binding with filling if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL) { //--- If failed to change the object size (for the entire underlay width and height), move on to the next one if(!obj.Resize(this.GetWidthUnderlay(),this.GetHeightUnderlay(),false)) continue; //--- Set the underlay as a binding object this.SetUnderlayAsBase(); //--- Get the object binding coordinates x=this.GetLeftObj().CoordX(); y=this.GetTopObj().CoordY(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; } //--- No binding if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_NONE) { //--- Reset the object size obj.Resize(obj.GetWidthInit(),obj.GetHeightInit(),false); //--- Get the initial object location coordinates x=this.GetCoordXUnderlay()+obj.CoordXRelativeInit(); y=this.GetCoordYUnderlay()+obj.CoordYRelativeInit(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; } //--- Calculate and set the relative object coordinates obj.SetCoordXRelative(x-this.m_underlay.CoordX()); obj.SetCoordYRelative(y-this.m_underlay.CoordY()); } } //--- Redraw the object with the redraw flag and return 'true' this.Redraw(redraw); return true; } //+------------------------------------------------------------------+
此处,我们可以在一个循环中完成所有操作,即不必把操作拆分为两个雷同的单独循环模块,还可附带检查面板自动调整大小标志。 如此即可把代码砍掉一半。 但我决定留着它,以防将来我必须完善处理面板自动调整大小标志的代码模块。 只有当所有的东西都调试好了,并且很明显不再需要更多的更改和改进时,我才会优化这个方法的代码。
调整元素大小以适应其内容的方法也已进行了修改。现在,我们就可以利用 CSelect 类获得必要的数据:
//+------------------------------------------------------------------+ //| Adjust the element size to fit its content | //+------------------------------------------------------------------+ bool CPanel::AutoSizeProcess(const bool redraw) { //--- Get the list of bound objects with WinForms type basic and higher CArrayObj *list=this.GetListWinFormsObj(); //--- Get object indices in the list with the maximum and minimum X and Y coordinates int imaxx=CSelect::FindGraphCanvElementMax(list,CANV_ELEMENT_PROP_COORD_X); int iminx=CSelect::FindGraphCanvElementMin(list,CANV_ELEMENT_PROP_COORD_X); int imaxy=CSelect::FindGraphCanvElementMax(list,CANV_ELEMENT_PROP_COORD_Y); int iminy=CSelect::FindGraphCanvElementMin(list,CANV_ELEMENT_PROP_COORD_Y); //--- Get objects with the maximum and minimum X and Y coordinates from the list CWinFormBase *maxx=list.At(imaxx); CWinFormBase *minx=list.At(iminx); CWinFormBase *maxy=list.At(imaxy); CWinFormBase *miny=list.At(iminy); //--- If at least one of the four objects is not received, return 'false' if(maxx==NULL || minx==NULL || maxy==NULL || miny==NULL) return false; //--- Get the minimum X and Y coordinate int min_x=minx.CoordX(); int min_y=fmin(miny.CoordY(),maxy.BottomEdge()); //--- Calculate the total width and height of all bound objects int w=maxx.RightEdge()-min_x; int h=int(fmax(miny.CoordY(),maxy.BottomEdge())-min_y); //--- Calculate the number of pixels, by which we need to resize the panel in width and height int excess_x=w-this.m_underlay.Width(); int excess_y=h-this.m_underlay.Height(); //--- Calculate the offset, by which the bound objects are to be moved int shift_x=m_underlay.CoordX()-min_x; int shift_y=m_underlay.CoordY()-min_y; //--- If failed to change the panel size, return 'true' if(excess_x==0 && excess_y==0) return true; //--- If it is necessary to move the attached objects inside the panel along the X or Y coordinate bool res=true; if(shift_x>0 || shift_y>0) { //--- In the loop by all attached objects, for(int i=0;i<list.Total();i++) { //--- get the next object. CWinFormBase *obj=list.At(i); if(obj==NULL) continue; //--- If the object needs to be shifted horizontally, write the shift result to 'res' if(shift_x>0) res &=obj.Move(obj.CoordX()+shift_x,obj.CoordY()); //--- If the object needs to be shifted vertically, write the shift result to 'res' if(shift_y>0) res &=obj.Move(obj.CoordX(),obj.CoordY()+shift_y); //--- Set new relative object X and Y coordinates obj.SetCoordXRelative(obj.CoordX()-this.m_underlay.CoordX()); obj.SetCoordYRelative(obj.CoordY()-this.m_underlay.CoordY()); } } //--- Return the result of resizing the panel return ( //--- If we failed to move at least one bound object, return 'false' !res ? false : //--- Otherwise, if only a size increase this.AutoSizeMode()==CANV_ELEMENT_AUTO_SIZE_MODE_GROW ? this.Resize(this.Width()+(excess_x>0 ? excess_x : 0),this.Height()+(excess_y>0 ? excess_y : 0),redraw) : //--- if both increase and decrease this.Resize(this.Width()+(excess_x!=0 ? excess_x : 0),this.Height()+(excess_y!=0 ? excess_y : 0),redraw) ); } //+------------------------------------------------------------------+
代码注释中完整描述了方法逻辑。 当获得最小 Y 轴坐标时,我偶尔会遇到与获得的值相冲突的奇怪行为... 第一次构造对象时,最大坐标会以最大值返回,而最小坐标则以最小值返回。 一切都是正确的。 然而,在重新排列对象后,将会在最小值返回最大坐标,而在最大值里返回最小坐标。 我还没有找到诱发这种行为的原因,我不得不在两个值之间做出选择 — 当请求最大值时,我们得到两个之中的最大值,而当请求最小值时,则得到两个值之中的最小值。
为什么要计算 X 和 Y 的偏移? 当把对象绑定到面板的右侧或底部时,它们是从面板的右侧边缘或底部边缘构建的。 因此,它们也许能够超出面板左侧或顶部。 由于面板(和任何其它图形元素)的坐标原点从左上角开始,因此当增加面板大小来适配内部所有对象的大小时,面板将向右或向下延展。 因此,面板的大小将对应于内部排布的所有对象,但面板坐标原点不会与所有对象的可见原点相对应。 因此,我们需要在对象绑定模式下根据计算出的偏移量将这些对象向右或向下移动。
返回 WinForms 基类型或更高类型的附着对象列表的方法:
//+------------------------------------------------------------------+ //| Return the list of bound objects | //| of WinForms base type and higher | //+------------------------------------------------------------------+ CArrayObj *CPanel::GetListWinFormsObj(void) { return CSelect::ByGraphCanvElementProperty(this.GetListElements(),CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE,EQUAL_OR_MORE); } //+------------------------------------------------------------------+
该方法简单地返回仅包含 WinForms 基类或更高级对象的列表,即基准 WinForms 对象或其衍生子对象。 对象则是利用 CSelect 类,从绑定到面板的所有对象的常规列表中按类型排序来选择。
我们找到 CGraphElementsCollection 类当中创建面板 WinForms 对象的方法,执行优化并剔除其中的逻辑错误,该类位于 \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh 文件中。
我犯了一个错误,因为对象边框绘制了两次,且可能没有为其设置边框颜色。 此外,如果给方法传递参数时,面板边框大小也许不会设置,而是采用默认值 -1。 我们并未检查该值,因此在设置边框大小时,采用默认值 -1 取而代之。
在所有面板创建方法中都进行了相同或雷同的修改:
//--- Create a WinForms Panel object graphical object on canvas on a specified chart and subwindow with the vertical gradient filling int CreatePanelVGradient(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 int frame_width=-1, ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL, 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); ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id); if(res==ADD_OBJ_RET_CODE_ERROR) return WRONG_VALUE; obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorsBackground(clr); obj.SetColorFrame(obj.ColorBackground()); obj.SetBorderStyle(frame_style); obj.SetOpacity(opacity,false); obj.SetFrameWidthAll(frame_width==WRONG_VALUE ? DEF_FRAME_WIDTH_SIZE : frame_width); //--- Draw the shadow drawing flag obj.SetShadow(shadow); if(shadow) { color clrS=obj.ChangeColorLightness(obj.ChangeColorSaturation(obj.ColorBackground(),-100),-20); obj.DrawShadow(3,3,clrS,CLR_DEF_SHADOW_OPACITY,DEF_SHADOW_BLUR); } obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); if(redraw) { obj.Erase(clr,opacity,true,false,redraw); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); } obj.SetActiveAreaShift(obj.FrameWidthLeft(),obj.FrameWidthBottom(),obj.FrameWidthRight(),obj.FrameWidthTop()); obj.Done(); return obj.ID(); }
将边框颜色设置为与面板背景颜色相同(设置为渐变的第一个颜色,或单色模式的唯一颜色)。 当设置边框大小时,检查传递给方法的数值。 如果为 -1,则默认值设置为 DEF_FRAME_WIDTH_SIZE 宏替换,该值在 Defines.mqh 中定义。 如果设置了重绘标志,则用背景色或渐变色绘制面板,并在顶部绘制一个矩形轮廓。 在此情况下,边框是在 CPanel 类的 virtual Erase() 方法中绘制的。
在创建面板的所有方法中都进行了这样的改进。 它们可以在文后所附的文件中找到。
测试
为了执行测试,我将借用前一篇文章中的 EA,并将其保存在 \MQL5\Experts\TestDoEasy\Part106\ 中,命名为 TestDoEasyPart106.mq5。
由于绑定到容器的面板对象现在默认创建时没有框架,故在 OnInit() 处理程序中为每个对象指定边框宽度和类型。 后续每个面板的颜色将根据循环索引计算的值变浅(背景颜色变浅的递减量等于循环索引 * 4)。 这是必要的,以便在改变面板的排布方式时能清晰地看到容器中面板排列的变化:
//+------------------------------------------------------------------+ //| 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 WinForms Panel object CPanel *pnl=NULL; pnl=engine.CreateWFPanel("WFPanel",50,50,230,150,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false); if(pnl!=NULL) { //--- Set Padding to 4 pnl.SetPaddingAll(4); //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs pnl.SetMovable(InpMovable); pnl.SetAutoSize(InpAutoSize,false); pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false); //--- In the loop, create 6 bound panel objects for(int i=0;i<6;i++) { //--- create the panel object with coordinates along the X axis in the center and 10 along the Y axis, the width of 80 and the height of 50 CPanel *prev=pnl.GetElement(i-1); int xb=0, yb=0; int x=(i<3 ? (prev==NULL ? xb : prev.CoordXRelative()) : xb+prev.Width()+20); int y=(i<3 ? (prev==NULL ? yb : prev.BottomEdgeRelative()+16) : (i==3 ? yb : prev.BottomEdgeRelative()+16)); if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,pnl,x,y,80,40,C'0xCD,0xDA,0xD7',200,true,false)) { CPanel *obj=pnl.GetElement(i); if(obj==NULL) continue; obj.SetFrameWidthAll(3); obj.SetBorderStyle(FRAME_STYLE_BEVEL); obj.SetColorBackground(obj.ChangeColorLightness(obj.ColorBackground(),4*i)); } } pnl.Redraw(true); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
编译 EA,并在品种图表上启动它:
如此,面板就能适应附着到其上的对象的正常大小,不同的绑定方法均能正确工作,面板也可以正确变更其大小。 当放置具有不同绑定方法的对象时,它们的排列方式与我的不同 — 它们不应附着到面板的边缘,如果列表中的每个后续对象具有相同的绑定,则应附着到前一个对象的边缘。 特别是,最后一个对象会被拉伸铺满容器的整个宽度和高度,这是不正确的;因为它应该限制在之前绑定到面板边缘的对象的内边缘,即,它应该设置在它们之间的自由空间内。 我将在后续文章中实现绑定对象的正确行为。
下一步是什么?
在下一篇文章中,我将继续研究面板对象,并着手开发新控件,包括 WinForms 文本标签对象。
*该系列的前几篇文章:
DoEasy. 控件 (第 1 部分): 第一步
DoEasy. 控件 (第 2 部分): 操控 CPanel 类
DoEasy. 控件 (第 3 部分): 创建绑定控件
DoEasy. 控件(第 4 部分):面板控件,Padding(填充)和 Dock(驻靠)参数
DoEasy. 控件 (第 5 部分): 基准 WinForms 对象,面板控件,AutoSize 参数
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/10989

